React useState with addEventListener

Viewed 1657

Can someone explain to me exactly why the callback from addEventListener after changing value (by click button) in useState from "init" to "new state" shows the value "init" while text in button is "new state"

function Test() {
    const [test, setTest] = useState("init")
    useEffect(() => {
        window.addEventListener("resize", () => console.log(test))
    }, [])

    return (
        <button style={{ marginTop: "100px" }} onClick={() => setTest("new state")}>
            {test}
        </button>
    )
}
3 Answers

Add test to the dependency array in your useEffect:

useEffect(() => {
  const func = () => console.log(test);
  window.addEventListener("resize", func)
  return () => {
    window.removeEventListener("resize", func)
  }
}, [test])

An explanation of why this is needed is covered in the Hooks FAQ: Why am I seeing stale props or state inside my function?:

another possible reason you’re seeing stale props or state is if you use the “dependency array” optimization but didn’t correctly specify all the dependencies. For example, if an effect specifies [] as the second argument but reads someProp inside, it will keep “seeing” the initial value of someProp. The solution is to either remove the dependency array, or to fix it.

Empty dependency array acts as componentDidMount. You should add test as one of the dependencies so it fires again

function Test() {
    const [test, setTest] = useState("init")
    useEffect(() => {
        window.addEventListener("resize", fun)
        return () => {
        window.removeEventListener("resize", fun)
    }
    }, [test])

    return (
        <button style={{ marginTop: "100px" }} onClick={() => setTest("new state")}>
            {test}
        </button>
    )
}

Another reason this is happening is the functional component is using stale state value.

Event is registered once on component mount with useEffect. It's the same function during entire component lifespan and refers to stale state that was fresh at the time when this eventListener was defined the first time.

The solution for that is using refs

Check out https://codesandbox.io/s/serverless-thunder-5vqh9 to understand the solution better

State inside an effect fired once is never updated (if you use [])

The value of "test" inside the scope of your useEffect is never updated since the effect was fired only once at mount. The State changes you make afterwards will never change this value unless you fire the Effect again but passing an argument into the square brackets.

Related