NotFoundError: Node was not found on removing react component - javascript

I want to display a component according to a state variable in the context provider. When I load this page, the closed is shown (fullRender is false initially). Clicking the button to display it, I see the canvas in the dom. When I click the Remove button, I call removeFullRender on the context which sets it false again.
const launchRender = () => {
setFullRender(true);
}
const removeFullRender = () => {
setFullRender(false);
}
As soon as I do this, react crashes (= shows white page and throws error in react-dom.development.js) with NotFoundError: Node was not found instead of showing the closed div again.
The full render component:
const FullRender = (props) => {
const context = useContext(FabricContext);
const [canvas, setCanvas] = useState();
useEffect(() => {
if(!context.fullRender){
return
}
setCanvas(new fabric.Canvas("full-canvas", {
selection: false,
}));
console.log(context.fullRender);
}, [context.fullRender]);
return (
<>
{context.fullRender ?
<>
<canvas
style={props.style}
id="full-canvas"
>
</canvas>
<Button onClick={context.removeFullRender}>X</Button>
</>
: <div>closed</div>
}
</>
);
}

I got it working in this example:
https://codesandbox.io/s/dank-frog-9wcgc?file=/src/App.js
The use of react fragments around the canvas caused the error (unsure why)
Changing to another tag should resolve it:
<div>
<canvas
style={props.style}
id="full-canvas"
>
</canvas>
<Button onClick={context.removeFullRender}>X</Button>
</div>

Related

How to render a new popup every time I clicked Grid?

The problem is...
The first popup renders fine.
But when I try to render the second popup, it's not working.
A new popup is not invoked, the previous popup is refreshed.
I want to call a new popup when I clicked a cell in the grid.
my code is like this
const Main = () => {
const [isPopupOpen, setIsPopupOpen] = useState(false);
return (
<>
... other components (including grid)
{ isPopupOpen && <Popup />}
</>
)
};
when Grid is Clicked, 'isPopupOpen' is updated to true.
I use 'react-new-window' library, and this library use 'window.open()' ((https://github.com/rmariuzzo/react-new-window)
so I set different window names to call several popups.
but I can't solve the problem.
I try to set a state object that has a boolean value.
const [popupObj, setPopupObj] = useState({});
when the grid is clicked, popupObj updates like
{'cellA': true, 'cellD': true}
and a return statement is like
{popupObj[cellName] && <Popup /> }
but the result was the same.
what should I do to solve this problem?
I wrote an example for you. Hope it helps.
use popupIds state to store the popups that you want to open
use Set to toggle the popupIds in the addPopup click handler
import * as React from "react";
export default function App() {
const [popupIds, setPopupIds] = React.useState([]);
const addPopup = (popupId) => {
const set = new Set(popupIds);
if (set.has(popupId)) {
set.delete(popupId);
} else {
set.add(popupId);
}
setPopupIds(Array.from(set));
};
return (
<div className="App">
{["hello", "react"].map((popupId) => (
<div onClick={() => addPopup(popupId)}>{popupId}</div>
))}
{popupIds.map((popupId) => (
<Popup title={getPopupTitle(popupId)} />
))}
</div>
);
}
const getPopupTitle = (popupId) => `title for ${popupId}`;
const Popup = ({ title }) => <div>{title}</div>;
Here is a codesandbox that you can play with directly.
You need to add your popup in an array, so you can render many popup as you want, then you need to define in How much time you will remove a added popup from array or add a close button
Extra: you can configure in global state to access in all your application to your popups and you will have a component like this: https://www.npmjs.com/package/notistack

Image is loading double time in React due to useState?

I have the following logic that renders an image, the current flow is like the following: if the state is false a spinner shows then when the image loads the spinner disappears.
The core problem here is the state is re-rendering the component causing the image to load again I kind of ran out of options, on how to make an instant switch after the image loads.
It is not a loop but rather the image reloads again even if it is already loaded due to the render caused by setLoading(true).
How to prevent this reloading from happening. The useEffect logic is just a simulator for how it might take to load the image, but my real image coms from the icons variable.
export const iconsImg: React.FC<Props> = ({ img: string }) => {
useEffect(() => {
let newImg = new Image();
newImg.src = icons[img].props.src;
newImg.onload = () => {
setLoading(true);
};
}, []);
const icons: iconsInterface = {
a: <img className={classes.imgStyle} alt="a" src={link} />,
b: <img className={classes.imgStyle} alt="b" src={link} />,
}
const [loading, setLoading] = useState(false);
return (
<React.Fragment>
{!loading ? (
<Spinner>
) : (
icons[img]
)}
</React.Fragment>
)}
Issue
The problem with your last attempt is that you are starting with loading showing the Spinner, and you are trying to switch the state after the image is loaded, except that will never be the case, because the image is not mounted in the DOM.
Creating icons is not equivalent to creating img until it's added in the return and mounted into the DOM. And I think it's loading twice cause at some point you added the two of them in the DOM either directly or after loading changes.
Solution
You can simplify your component as below. Notice I removed icons and setting alt attribute (the one difference between the two) while creating the image. That setTimeout is so we see the loader. You can remove it later.
const IconsImg = ({ img }) => {
const [loading, setLoding] = React.useState(true);
return <div >{loading && "Loading..."} <img alt={img} style={{display: loading ? "none" : "block"}} onLoad= {()=> setTimeout(()=> setLoding(false), 1000)} src = "https://images.unsplash.com/photo-1657664042206-1a98fa4d153d?ixlib=rb-1.2.1&ixid=MnwxMjA3fDF8MHxlZGl0b3JpYWwtZmVlZHwxfHx8ZW58MHx8fHw%3D&auto=format&fit=crop&w=500&q=60"/></div>;
};
/* The below code is to have a working example here at Stack Overflow */
ReactDOM.render(
<IconsImg img= "car" />,
document.getElementById("root")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.0/umd/react-dom.production.min.js"></script>
<div id="root"></div>
newImg is a different object from icons[img]. When loading set
to true, browser loads same src this time for icons[img].
You can try this:
First set display:none for icons[img] if loading is false;
And:
<React.Fragment>
{loading || (
<Spinner>
)}
icons[img]
</React.Fragment>
Then:
useEffect(() => {
icons[img].onload = () => {
setLoading(true);
};
}, []);
or better:
<img className={classes.imgStyle} alt="a" src={link} onload={() => setLoading(true)}/>

Render heavy React component in the background

I have a recursive component that takes a couple seconds to render because of its complexity. I inspected the DOM and it turned out the component that contains that heavy component doesn't get inserted until after it fully loads. Before that happens, the page is just blank.
const SomeElement = (props) => {
// ...
return (
<div> // not inserted into DOM until HeavyComponent fully loads
// expected behavior: First the Loading... label is displayed, then the contents of the HeavyComponent
// actual behavior: not rendered until HeavyComponent renders, thus Loading... label and the component shows up at the same time
<div>Loading...</div>
<HeavyComponent />
</div>
);
};
I would like to display Loading... message when the other component loads in the background, like:
const [ heavyComponent, setHeavyComponent ] = useState(null);
React.asyncCreateElement(HeavyComponent, props)
.then((loadedComponent) => {
setHeavyComponent(loadedComponent)
})
if (!heavyComponent) {
return <div>Loading...</div>
}
return <div> { heavyComponent } </div>;
The closest I could find is React.lazy and Suspense, but it doesn't really match my usecase - I want that HeavyComponent to always be always visible. Using code-splitting didn't change the behavior.
So to reiterate: Is there a way to render a heavy (not because of async, but because of its complexity) component in the background (like in a service worker)?
Could you not just do
const SomeElement = (props) => {
const [firstRender, setFirstRender] = useState(true);
useEffect(() => {
setFirstRender(false);
}, []);
return (
<div>
{ firstRender && <div>Loading...</div> }
{ !firstRender && <HeavyComponent /> }
</div>
);
};
so that you get the render you want, and then once it has finished generating the heavy component will replace it?

React: override internal components with custom component

I have a modal that is completely self contained. The modal is opened via going to the modal route and all the functionality to close the modal from button or outside clicks is within the modal component. Basically the modal is not controlled by any parent passing state. I was given a task of making the modals button customizable, meaning passing in a new button component, so we can add the modal to our lib instead of copy pasting the code in projects. Lol this seemed simple enough, and maybe it is and I am just overthinking this.
I cant paste the actual code but I can use a contrived example. This is a very simplified version of the modal, keeping in mind it opens via route so there's really no state and setState in the actual code. Also here is a fiddle
const ModalHeader = ({ onClose }) => {
return (
<div className="modal__header">
<button
className="modal__close-btn"
data-testid="modal-close-button"
onClick={onClose}
/>
</div>
);
};
const Modal = ({ children }) => {
const [state, setState] = React.useState(true);
const handleCloseOutsideClick = () => {
setState(false);
};
const handleCloseButtonClick = () => {
setState(false);
};
const renderModal = () => {
return (
<div className="modal-overlay" onClick={handleCloseOutsideClick}>
<div className="modal">
<ModalHeader onClose={handleCloseButtonClick} />
{children}
</div>
</div>
);
};
return state ? renderModal() : null;
};
const App = () => {
return (
<Modal>
<div>Modal Children</div>
</Modal>
);
};
ReactDOM.render(<App />, document.querySelector('#app'));
I tried a few things, initially I attempted to find a way to pass in a new header component containing a button. Then as I got into the code I realized what I was doing would lose the self contained functionality of the modal. My approach was along the lines of below but obviously the onClick would be an issue since invoking the close functionality is internal.
So I tried using cloneElement to add props within the component if the custom header was detected:
// inside modal component
React.useEffect(() => {
React.Children.map(children, (child: React.ReactElement) => {
if (child && child.type === ModalHeader) {
setHederFound(true);
}
});
}, []);
// inside modal render:
<div className={modalClasses} onClick={stopPropagation}>
{!headerFound ? (
<ModalDefaultHeader onClose={handleCloseButtonClick} />
) : (
React.Children.map(children, (child: React.ReactElement) => {
if (child && child.type === ModalHeader) {
return React.cloneElement(child, {
onClose: handleCloseButtonClick,
});
}
})
)}
{children}
</div>;
Obviously that did not work because there's no onClick in the custom button. Anyways I am thinking that I am over complicating this. I just need a way to pass in a custom button while leaving the functionality internal to the modal. Any assistance would be appreciated.
Thanks in advance.

audio.play is not a function error from arrow function component using ref

I'm working on the freeCodeCamp drum machine project. When the user clicks the drum pad, I'd like a sound to play. Since the audio element isn't the real DOM element but a copy of it, to refer to it I need to use a ref (or at least I think that's why).
I followed the React doc instructions for using a ref in a function component, and I have a method handleClick that's triggered when you click a "drum pad" which I'd like to call audio.play(). However, when I click one of the drum pads, the app crashes with an error "audio.play is not a function." Is it a problem with the ref? Any suggestions appreciated.
Child component DrumPad.js:
const DrumPad = ({ id, letter, src }) => {
let audio = React.createRef(); // <--
const handleClick = () => { // <--
audio.play();
}
return (
<div
className="drum-pad"
id={id}
onClick={handleClick}
>
<p>{letter}</p>
<audio
ref={audio} // <--
id={letter}
src={src}
>
</audio>
</div>
);
}
Parent App.js:
const sounds = [
{ id: 'snare', letter: 'Q', src: 'https://www.myinstants.com/media/sounds/snare.mp3' },
// etc.
];
const App = () => {
return (
<div className="drum-machine">
<div className="drum-pads">
{sounds.map(sound => (
<DrumPad
id={sound.id}
letter={sound.letter}
src={sound.src}
/>
))}
</div>
</div>
);
}
The DOM node is actually store under the current key.
This should fix your problem:
const handleClick = () => {
audio.current.play();
}
React docs about createRef

Categories

Resources