I've a state named modal
in my React App. The initial value is an object that says {show: false, photo: null}
.
I've two buttons in the page. One is calling the close
function and another is calling the open
function. open
is setting the state to {show: true, photo: true}
and close
is just logging modal
I also wrote some code to call the close
function when the Esc
button is clicked.
Here's my code:
function App() { const [modal, setModal] = useState({ show: false, photo: null }); // open func function open() { setModal({ show: true, photo: true }); } // close func function close() { console.log(modal); } // function for esc key press function escFunc(event) { if (event.key === `Escape`) { close(); } } useEffect(() => { document.addEventListener(`keydown`, escFunc, true); return () => { document.removeEventListener(`keydown`, escFunc, true); }; }, []); return ( <> <button onClick={open}>open</button> <br /> <button onClick={close}>close</button> </> ); }
so now when I click the open
button and then click the close button, it's logging {show: true, photo: true}
(as expected). but the problem comes in if I press Esc
now. It should log {show: true, photo: true}
(as the state is already updated by the open
function), but it's logging {show: false, photo: null}
as if the state hasn't changed yet
Why is it happening?
Whenever a component rerenders, the entire function is reran.
In your useEffect
, which is only called on the first render, you call document.addEventListener
with the callback function escFunc
. This escFunc
has a closure that stores the value of modal
, which is a reference to the original object state { show: false, photo: null }
.
In your open
function, you set the state to { show: true, photo: true }
using the object literal syntax, which creates a whole new object with a new reference location.
The event listener is still tracking the original object.
To be able to get the new state reference, you need to remove the old event listener and then add a new event listener.
There are multiple ways to do this.
useEffect(() => {
document.addEventListener(`keydown`, escFunc, true);
return () => {
document.removeEventListener(`keydown`, escFunc, true);
};
}, [modal]); // add modal to dep array
useEffect(() => {
document.addEventListener(`keydown`, escFunc, true);
return () => {
document.removeEventListener(`keydown`, escFunc, true);
};
}, [escFunc]); // add escFunc to dep array, but this would run every render
Stylistically, this is the best option because it properly shows dependencies and doesn't have extra rerenders, but the calls to useCallback
might make it slower
const close = useCallback(function() {
console.log(modal);
}, [modal]); // depends on modal
const escFunc = useCallback(function(event) {
if (event.key === `Escape`) {
close();
}
}, [close]); // depends on close
useEffect(() => {
document.addEventListener(`keydown`, escFunc, true);
return () => {
document.removeEventListener(`keydown`, escFunc, true);
};
}, [escFunc]); // add escFunc to dep array
In fact, you don't even need to have escFunc
outside of useEffect
if you don't use it elsewhere
const close = useCallback(function() {
console.log(modal);
}, [modal]); // depends on modal
const escFunc = useCallback(function(event) {
if (event.key === `Escape`) {
close();
}
}, [close]); // depends on close
useEffect(() => {
function escFunc(event) {
if (event.key === `Escape`) {
close();
}
}
document.addEventListener(`keydown`, escFunc, true);
return () => {
document.removeEventListener(`keydown`, escFunc, true);
};
}, [close]); // add escFunc to dep array
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.