I need to pass the event inside onCollide function to the parent component. I have tried with props.handleCollide(e); but I got an error: props.handleCollide is not a function. If it helps, I'm using React Three Fiber, a React library for Three.js.
Sphere.js
const Sphere = (props) => {
const [ref, api] = useSphere((index) => ({
mass: 1,
onCollide: (e) => {
// I need to pass the event 'e' to Parent Component
props.handleCollide(e);
},
position: props.position,
...props,
}));
return (
<mesh castShadow position={props.position} ref={ref}>
<sphereGeometry args={props.args} />
<meshStandardMaterial color={props.color} />
</mesh>
);
}
export default Sphere;
Another attempt was to do props.event = e but I got other error: Cannot add property event, object is not extensible.
The main goal is to do something like this:
<Sphere position={[0, 3, 0]} args={[0.5]} color="purple" handleCollide={handleCollide} />
And then catch the event and do the logic.
Related
I am getting the following error during sonarqube scan:
Do not define components during render. React will see a new component type on every render and destroy the entire subtree’s DOM nodes and state. Instead, move this component definition out of the parent component “SectionTab” and pass data as props. If you want to allow component creation in props, set allowAsProps option to true.
I understand that it says that I should send the component as a prop from the parent, but I don't want to send the icon everytime that I want to use this component, is there another way to get this fixed?
import Select from "#mui/material/Select";
import { FontAwesomeIcon } from "#fortawesome/react-fontawesome";
import { faAngleDown } from "#fortawesome/pro-solid-svg-icons/faAngleDown";
const AngleIcon = ({ props }: { props: any }) => {
return (
<FontAwesomeIcon
{...props}
sx={{ marginRight: "10px" }}
icon={faAngleDown}
size="xs"
/>
);
};
const SectionTab = () => {
return (
<Select
id="course_type"
readOnly={true}
IconComponent={(props) => <AngleIcon props={props} />}
variant="standard"
defaultValue="cr"
disableUnderline
/>
);
};
export default SectionTab;
What can you do:
Send the component as the prop:
IconComponent={AngleIcon}
If you need to pass anything to the component on the fly, you can wrap it with useCallback:
const SectionTab = () => {
const IconComponent = useCallback(props => <AngleIcon props={props} />, []);
return (
<Select
id="course_type"
readOnly={true}
IconComponent={IconComponent}
variant="standard"
defaultValue="cr"
disableUnderline
/>
);
};
This would generate a stable component, but it's pretty redundant unless you need to pass anything else, and not via the props. In that case, a new component would be generated every time that external value changes, which would make it unstable again. You can use refs to pass values without generating a new component, but the component's tree won't be re-rendered to reflect the change in the ref.
const SectionTab = () => {
const [value, setValue] = useState(0);
const IconComponent = useCallback(
props => <AngleIcon props={props} value={value} />
, []);
return (
<Select
id="course_type"
readOnly={true}
IconComponent={IconComponent}
variant="standard"
defaultValue="cr"
disableUnderline
/>
);
};
Hoping someone can help me out here.
Im quite new to RecoilJS so if Im missing something obvious, please let me know.
I am trying to manage the state of 3D objects in a scene with RecoilJS Atoms.
I have an atom for the last item the mouse hovered over and I want to show a debug panel with its info.
For some reason the RecoilRoot provider doesn't seem to be accessible from within the ThreeJS canvas.
In Viewer (code below), I get an error warning me that This component must be used inside a <RecoilRoot> component when I try to declare const [hoveredLED, setHoveredLEDAtom] = useRecoilState(hoveredLEDAtom); (full trace below)
However, passing setHoveredLEDAtom down from the parent (Viewer) works.
Declaring it within Debug also works, which is a sibling of Canvas sharing the same contexts
This is fine for now, but the whole point of moving to Recoil was to stop passing props up and down.
Am I missing something obvious or does the ThreeJS canvas somehow exist in a different scope.
Any pointers would be appreciated.
index.js
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
// <React.StrictMode>
<RecoilRoot>
<App />
</RecoilRoot>
// </React.StrictMode>
App.js
function App() {
return (
<div className="App">
<Viewer />
</div>
);
}
Viewer
const LED = ({ led }) => {
const [hovered, setHoevered] = useState(false);
const [hoveredLED, setHoveredLEDAtom] = useRecoilState(hoveredLEDAtom);
const handleHoverEnter = () => {
setHoveredLEDAtom(led);
setHoevered(true);
};
const handleHoverExit = () => {
setHoevered(false);
};
return (
<mesh
onPointerOver={(event) => handleHoverEnter()}
onPointerOut={(event) => handleHoverExit()}
>
<coneGeometry />
<meshStandardMaterial
color={hovered || led.brightness > 125 ? "hotpink" : "grey"}
/>
</mesh>
);
};
const Debug = () => {
const [hoveredLED, setHoveredLEDAtom] = useRecoilState(hoveredLEDAtom);
return (
<>
<div style={{ position: "absolute", left: "10px", top: "1rem" }}>
member : {hoveredLED.member}
</div>
</>
);
};
const Viewer = () => {
const [model, setModel] = useRecoilState(modelAtom);
const [hoveredLED, setHoveredLEDAtom] = useRecoilState(hoveredLEDAtom);
useEffect(() => {
console.log(hoveredLED);
}, [hoveredLED]);
return (
<>
<Debug />
<Canvas camera={{ position: [5, 7, 5] }} style={{ height: "700px" }}>
<Helpers />
<OrbitControls />
{model.map((led, index) => {
const key = `led-${index}`;
return (
<LED key={key} led={led} />
);
})}
</Canvas>
</>
);
};
export default Viewer;
Error
995 react-reconciler.development.js:9747 The above error occurred in the <LED> component:
at LED (http://localhost:3000/static/js/bundle.js:205:5)
at Suspense
at ErrorBoundary (http://localhost:3000/static/js/bundle.js:1998:5)
at Provider (http://localhost:3000/static/js/bundle.js:3860:5)
React will try to recreate this component tree from scratch using the error boundary you provided, ErrorBoundary.
logCapturedError # react-reconciler.development.js:9747
2 react-three-fiber.esm.js:141 Uncaught Error: This component must be used inside a <RecoilRoot> component.
at err (recoil.js:16:1)
at Object.notInAContext (recoil.js:4092:1)
at updateRetainCount (recoil.js:3255:1)
at useRetain_ACTUAL (recoil.js:4669:1)
at useRetain (recoil.js:4627:1)
at useRecoilValueLoadable (recoil.js:5234:1)
at useRecoilValue (recoil.js:5258:1)
at useRecoilState (recoil.js:5306:1)
at LED (Viewer.js:76:1)
at renderWithHooks (react-reconciler.development.js:7363:1)
react-dom.development.js:18525 The above error occurred in the <ForwardRef(Canvas)> component:
at Canvas (http://localhost:3000/static/js/bundle.js:4179:5)
at Viewer (http://localhost:3000/static/js/bundle.js:325:83)
at div
at App
at RecoilRoot_INTERNAL (http://localhost:3000/static/js/bundle.js:91093:5)
at RecoilRoot (http://localhost:3000/static/js/bundle.js:91259:5)
Consider adding an error boundary to your tree to customize error handling behavior.
Visit https://reactjs.org/link/error-boundaries to learn more about error boundaries.
logCapturedError # react-dom.development.js:18525
react-dom.development.js:26740 Uncaught Error: This component must be used inside a <RecoilRoot> component.
at err (recoil.js:16:1)
at Object.notInAContext (recoil.js:4092:1)
at updateRetainCount (recoil.js:3255:1)
at useRetain_ACTUAL (recoil.js:4669:1)
at useRetain (recoil.js:4627:1)
at useRecoilValueLoadable (recoil.js:5234:1)
at useRecoilValue (recoil.js:5258:1)
at useRecoilState (recoil.js:5306:1)
at LED (Viewer.js:76:1)
at renderWithHooks (react-reconciler.development.js:7363:1)
three.module.js:26599 THREE.WebGLRenderer: Context Lost.
From :
https://recoiljs.org/docs/api-reference/core/useRecoilBridgeAcrossReactRoots/
If a nested React root is created with ReactDOM.render(), or a nested
custom renderer is used, React will not propagate context state to the
child root. This hook is useful if you would like to "bridge" and
share Recoil state with a nested React root. The hook returns a React
component which you can use instead of in your nested
React root to share the same consistent Recoil store state. As with
any state sharing across React roots, changes may not be perfectly
synchronized in all cases.
So bridging the contexts is a simple as creating a bridge and nesting it within the ThreeJS Canvas
const Viewer = () => {
const RecoilBridge = useRecoilBridgeAcrossReactRoots_UNSTABLE();
...
return (
<>
<Canvas>
<RecoilBridge>
...
</RecoilBridge>
</Canvas>
</>
);
};
export default Viewer;
I'm having issues trying to get my useState variable to work. I create the state in my grandparent then pass it into my parent. Here's a simplified version of my code:
export function Grandparent(){
return(
<div>
const [selectedID, setSelectedID] = useState("0")
<Parent setSelectedID2={setSelectedID} .../> //(elipses just mean that I'm passing other params too)
<div />
)}
Parent:
const Parent = ({setSelectedID2 ...}) => {
return(
<div>
{setSelectedID2("5")} //works
<Child setSelectedID3={setSelectedID2} />
</div>
)
}
From the parent I can use 'setSelectedID2' like a function and can change the state. However, when I try to use it in the child component below I get an error stating 'setSelectedID3' is not a function. I'm pretty new to react so I'm not sure if I'm completely missing something. Why can I use the 'set' function in parent but not child when they're getting passed the same way?
Child:
const Child = ({setSelectedID3 ...}) => {
return(
<div >
{setSelectedID3("10")} //results in error
</div>
);
};
In React you make your calculations within the components/functions (it's the js part) and then what you return from them is JSX (it's the html part).
export function Grandparent(){
const [selectedID, setSelectedID] = useState("0");
return(
<div>
<Parent setSelectedID2={setSelectedID} .../> //(elipses just mean that I'm passing other params too)
<div />
)}
You can also use (but not define!) some js variables in JSX, as long as they are "renderable" by JSX (they are not Objects - look for React console warnings).
That's your React.101 :)
Here's a working example with everything you have listed here. Props are passed and the function is called in each.
You don't need to name your props 1,2,3.., they are scoped to the function so it's fine if they are the same.
I moved useState and function calls above the return statement, because that's where that logic should go in a component. The jsx is only used for logic dealing with your display/output.
https://codesandbox.io/s/stupefied-tree-uiqw5?file=/src/App.js
Also, I created a working example with a onClick since that's what you will be doing.
https://codesandbox.io/s/compassionate-violet-dt897?file=/src/App.js
import React, { useState } from "react";
export default function App() {
return <Grandparent />;
}
const Grandparent = () => {
const [selectedID, setSelectedID] = useState("0");
return (
<div>
{selectedID}
<Parent setSelectedID={setSelectedID} selectedID={selectedID} />
</div>
);
};
const Parent = ({ selectedID, setSelectedID }) => {
setSelectedID("5");
return (
<div>
{selectedID}
<Child setSelectedID={setSelectedID} selectedID={selectedID} />
</div>
);
};
const Child = ({ selectedID, setSelectedID }) => {
setSelectedID("10");
return <div>{selectedID}</div>;
};
output
10
10
10
const [selectedID, setSelectedID] = useState("0")
should be outside return
In my App component, I have 2 components Navbar and View. In my Navbar component, I have an ExportButton component which onClick should generate a screenshot of the View component by passing its ref.
App.js
function App() {
const view = useRef();
return (
<div className="App">
<Navbar takeSnap={view}/>
<View ref={view}/>
</div>
);
}
Navbar.js
const Navbar = ({ takeSnap }) => {
return (
<>
<Lists />
<ExportButton takeSnap={takeSnap} />
</>
);
};
Button.js
const ExportButton = ({ takeSnap }) => {
function handleClick(takeSnap) {
domtoimage.toBlob(takeSnap.current, {}).then(function (blob) {
saveAs(blob, "myImage.png");
});
}
return (
<Button onClick={() => handleClick(takeSnap)} />
);
};
I having some trouble passing ref of View to use the library dom-to-image to take a screenshot. The error says "Uncaught (in promise) TypeError: Cannot read property 'cloneNode' of undefined
at makeNodeCopy". This might be a quick fix but I'm not sure where I'm going wrong.
You cannot create a ref for a component, a ref can only reference a DOM element.
When you do:
<View ref={view}/>
ref is a reserved keyword and it won't be passed down to your View render function.
You can use forwardRef to solve this problem, or simply use a different keyword such as myRef:
<View myRef={view}/>
Then when you render your View, you can assign this ref to the element you want the screenshot from:
<div ref={myRef} ...
I have a Slider component that doesn't handle its own state, instead, it's wrapped in another component called SliderDrag that manages the state and passes down props to the Slider
I'm using withState from recompose library to achieve that.
here is my Slider state initial state
export const getInitialState = () => ({
min: 0,
max: 100,
value: 50,
step: 1,
width: 100,
dragging: false
});
And here is my Higher Order Component from withState()
export const SliderDrag = withState('state', 'onChange', getInitialState())(
({state, onChange, action}) => (
<Slider
{...state}
onDragStart={({x}) => {
onChange(dragStart(x, state));
}}
onDrag={({x}) => {
onChange(drag(x, state));
action('value')(state.value);
}}
onDragEnd={() => {
onChange(dragEnd(state));
}}
/>
)
);
And here is how I want to use SliderDrag. I want to pass down props to set my initial state so that I can use this Slider component in different contexts in my app
export const interactive = (action) => (
<Storypage theme={designerTheme}>
<SliderDrag width={218} step={1} min={0} max={100} action={action}/>
</Storypage>
);
Do I need to use compose from recompose library? Any advice or help would be highly appreciated. :)
With some help I was able to solve my problem.
I should pass a reference to the function that sets the initialState as the last argument to withState(), this way when initialState gets called, I will have access to the props I'm passing to my SliderDrag component
Here is how I refactored this
const initialState = ({min, max, step, width, value}) => {
debugger;
return defaults({min, max, step, width, value}, defaultState());
};
export const SliderDrag = withState('state', 'onChange', initialState)(
({state, onChange, action}) => (
<Slider
{...state}
...
/>
)
);
defaults here just overrides the values I'm passing and gives me a new object as my state.
This makes my component configurable because any other component can use it and pass down props as the initial state