React: Event handler in addEventListener doesn't have access to the latest state

Created on 25 Jan 2019  Â·  3Comments  Â·  Source: facebook/react

I'm using react 16.8.0-alpha.1 with hooks and typescript and noticed this strange behavior. If I attach an event listener to the document or other element using the addEventListener method, the event handler method doesn't have access to the latest state variables.

I have created a codepen to reproduce the issue. The problem is that inside the handleScroll method the scrolled variable is always false, but in the useEffect method the variable is true.

Steps to reproduce:
1) Go to codepen.
2) Open the console.
3) Scroll the page.
4) Check the console output to see the different output from handleScroll and useEffect.

Most helpful comment

If you use a local variable or function in useEffect, you must specify it in dependencies array — or leave the array out completely.

  React.useEffect(() => {
    document.addEventListener("scroll", handleScroll); // <---- you're closing over handleScroll

    return () => document.removeEventListener("scroll", handleScroll);
  }, []); // <--------- empty array 

The simple fix is to just remove the array. Remember that useEffect doesn't block paint and addEventListener is very fast. It's no big deal to re-execute it once in a while. Another way to fix your particular example is to move handleScroll inside your effect, and specify scrolled as a dependency in the array.

For the rare cases where some value changes very often but re-running the effect is very undesirable (e.g. because setting up a subscription itself has side effects or is expensive), you can use a mutable ref as escape hatch.

All 3 comments

If you use a local variable or function in useEffect, you must specify it in dependencies array — or leave the array out completely.

  React.useEffect(() => {
    document.addEventListener("scroll", handleScroll); // <---- you're closing over handleScroll

    return () => document.removeEventListener("scroll", handleScroll);
  }, []); // <--------- empty array 

The simple fix is to just remove the array. Remember that useEffect doesn't block paint and addEventListener is very fast. It's no big deal to re-execute it once in a while. Another way to fix your particular example is to move handleScroll inside your effect, and specify scrolled as a dependency in the array.

For the rare cases where some value changes very often but re-running the effect is very undesirable (e.g. because setting up a subscription itself has side effects or is expensive), you can use a mutable ref as escape hatch.

@gaearon Could you kindly explain the difference between putting handleScroll inside and outside the effect?

Adding the state into the dependency array worked for me

Was this page helpful?
0 / 5 - 0 ratings

Related issues

gaearon picture gaearon  Â·  126Comments

gaearon picture gaearon  Â·  104Comments

gabegreenberg picture gabegreenberg  Â·  119Comments

gaearon picture gaearon  Â·  111Comments

gabegreenberg picture gabegreenberg  Â·  264Comments