I have a popup that can appear on my screen, whether it appears or not is controlled by a state. When the popup appears, I would like to be able to click anywhere on the screen to make it disappear. I was thinking of the following:
function App() {
const [isPopup, setPopup] = useState(false);
useEffect(() => {
if (!isPopup) { // if the popup just turned off, do nothing
return;
}
const handleClick = () => {
setPopup(false);
};
window.addEventListener('click', handleClick);
return () => window.removeEventListener('click', handleClick);
}, [isPopup]);
This should do the following:
When the popup state changes, run the useEffect. If the popup state is true (meaning the popup just turned on), then create a handler that turns it off, and add that handler to respond to a mouse click. When the component un-mounts, remove it.
However, I believe this may create a few issues:
- If we add event listeners every time the popup state is true, that seems bad? I think it won't actually add duplicate ones, but it suggests inefficient code design.
- Similarly, removing event listeners only on unmount seems bad, as opposed to when we click to remove the popup.
- Running the useEffect each time we change the popup is inefficient, as half the time we are changing the popup to false, and we just return immediately.
How can I solve for the above three issues?
CodePudding user response:
You don't need to use useEffect at all.
function App() {
const [isPopup, setPopup] = useState(false);
function handleClick(e){
if(e.target.id === "popup" && !isPopup) setPopup(true)
if(e.target.id !== "popup" && isPopup) setPopup(false)
}
return <main id="main" onClick={handleClick}>
{isPopup && <div id="popup" onClick={handleClick}></div>}
///Your JSX here
</main>
}
One function can handle both cases in two lines. Just make it sure that tag occupies all the screen available. And use UseEffect only for actual effects.
CodePudding user response:
- instead of using useEffect you may use useRef hook to access dom objects without re-rendering the application which is more efficient.
const eventListenerAdded = useRef(false);
useEffect(() => { if (!isPopup || eventListenerAdded.current) { // if the popup just turned off, or the event listener has already been added, do nothing return; }
const handleClick = () => {
setPopup(false);
};
window.addEventListener('click', handleClick);
eventListenerAdded.current = true;
return () => window.removeEventListener('click', handleClick);
}, [isPopup]);
const eventListenerAdded = useRef(false);
useEffect(() => {
if (!isPopup || eventListenerAdded.current) { // if the popup just turned off, or the event listener has already been added, do nothing
return;
}
const handleClick = () => {
setPopup(false);
};
window.addEventListener('click', handleClick);
eventListenerAdded.current = true;
return () => window.removeEventListener('click', handleClick);
}, [isPopup]);
- To remove the event listener when the popup is closed, you can add a cleanup function to the useEffect that removes the event listener when the popup is closed.
return () => {
window.removeEventListener('click', handleClick);
eventListenerAdded.current = false;
}
3.To prevent the useEffect from running when the popup is turned off, you can add a dependency to the useEffect that only causes it to run when the popup is turned on.
useEffect(() => {
}, [isPopup, eventListenerAdded]);
CodePudding user response:
You don't need a useEffect for this example. I share an example for you. You can copy paste and test it.
import * as React from 'react';
export default function App() {
const [show, setShow] = React.useState(false);
return (
<main
style={{ height: '100vh', width: '100vh', position: 'relative' }}
onClick={() => {
setShow(false);
}}
>
<button
onClick={(e) => {
e.stopPropagation();
setShow(true);
}}
>
Show
</button>
{show && (
<div
style={{
top: 0,
left: 0,
position: 'fixed',
opacity: '.8',
backgroundColor: '#dedede',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
width: '100vh',
height: '100vh',
}}
>
<div
style={{
width: 200,
height: 200,
backgroundColor: 'blue',
}}
onClick={(e) => {
e.stopPropagation();
}}
>
Modal active area. Model close event is should not be trigger in this area.
</div>
</div>
)}
</main>
);
}