简体   繁体   中英

why this function is behaving weirdly?

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM