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} ...
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;
When trying to pass a component as a prop of another component, everything works fine.
But if i want instead pass a Component and handle its css classes inside the children, I'm currently lost.
In my mind im trying to achieve something similar to this:
import Navbar from 'what/ever/path/Navbar/is/in/Navbar.js';
export default function ParentComponent {
return(
<Navbar NavIcon={<MyIcon/>} />
)
}
.... Imports etc...
export default function Navbar(props) {
const {NavIcon} = props;
return(
<Navigation>
// Now use the Prop as a Component and pass default classNames to it.
// So that we don't need to wrap everything inside a span / div etc.
<NavIcon className="AddCustomStylesAlwaysHere" />
</Navigation>
)
}
Two approaches come to my mind:
Passing a component
Just pass the component and let the parent take care of its instantiation. This way, the only changes you need is making sure <MyIcon /> accepts a className prop:
const MyIcon = ({ className }) => {
return <div className={className} />
};
const Navbar = ({ NavIcon }) => {
return (
<Navigation>
<NavIcon className="AddCustomStylesAlwaysHere" />
</Navigation>
);
};
<Navbar NavIcon={MyIcon} />
Passing an element instance
This way, you take care of instantiating the component and the parent just renders it. In this case, you have to use React utilities to modify existing elements (https://reactjs.org/docs/react-api.html#cloneelement):
const MyIcon = ({ className }) => {
return <div className={className} />
};
const Navbar = ({ NavIcon }) => {
return (
<Navigation>
{React.cloneElement(NavIcon, { className: 'AddCustomStylesAlwaysHere' })}
</Navigation>
);
};
<Navbar NavIcon={<MyIcon />} />
You can use React.Children.map in combination with React.cloneElement:
{
React.Children.map(children, ( child, idx ) => {
return React.cloneElement(child, { className: 'additional-classnames' })
})
}
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
I would like to access a ref passed between two external components using render props (real example). Can it be done?
function Component() {
// how to get access to `ref` here?
return (
<A>
{({ref}) => (
<B ref={ref}/>
)}
</A>
)
}
You may need React.forwardRef
Ref forwarding is a technique for automatically passing a ref through a component to one of its children.
This is typically not necessary for most components in the application. However, it can be useful for some kinds of components, especially in reusable component libraries.
const FancyButton = React.forwardRef((props, ref) => (
<button ref={ref} className="FancyButton">
{props.children}
</button>
));
const ref = React.createRef();
<FancyButton ref={ref}>Click me!</FancyButton>;
Figured it out. The ref render prop is actually badly named, it's not a ref but a function to set a ref, so we can just use a inline function (thought this may cause extra renders):
function Component() {
const bRef = useRef(null);
return (
<A>
{({ref: setRef}) => (
<B ref={ref => {
bRef.current = ref;
setRef(ref);
}}/>
)}
</A>
)
}