Ashish C
Ashish C

Reputation: 31

Updated useState value not showing in another function

In the sample code var1 variable latest value is not accessible inside the event listener for 'my_event'. setTimeout updates the var1 value to ['a','b'] but when doSomething function is called the value in the function onMyEvent still prints the initial var1 value []

Any insight into the issue and fix will be very helpful. Thanks

Code sample

import { useEffect, useState } from 'react';

function App() {
  const [var1, setVar1] = useState([])

  const onMyEvent = (e)=>{
    console.log("Detail var1 in onMyEvent: ", var1)//var1 value still prints as [] and not ['a','b']
  }
  useEffect(()=>{
    document.addEventListener('my_event', onMyEvent);
    setTimeout(()=>setVar1(['a','b']),1000)
  },[])
  const doSomething = ()=>{
    document.dispatchEvent(new CustomEvent('my_event', { detail: 5 }));
  }
  return (
    <div className="App">
      <div>{var1.toString()}</div><br/>
      <button onClick={doSomething}>Show Data</button>
    </div>
  );
}

export default App;

Upvotes: 0

Views: 792

Answers (2)

Arthur Costa
Arthur Costa

Reputation: 1549

You need to add var1 into the array of dependencies in useEffect. That way every time the variable var1 changes, what's inside useEffect will be reevaluated.

Every time the var1 changes, it will create an event listener to your custom event for onMyEvent function with the correct value for var1. Also, to prevent creating multiple event listeners unintentionally, one can use the return function to unsubscribe to the event once the var1 changes again.

import { useEffect, useState } from 'react';

function App() {
  const [var1, setVar1] = useState([])

  const onMyEvent = (e)=>{
    console.log("Detail var1 in onMyEvent: ", var1)//var1 value still prints as [] and not ['a','b']
  }

  useEffect(()=>{
    document.addEventListener('my_event', onMyEvent);
    setTimeout(()=>setVar1(['a','b']),1000)
    return () => {
        document.removeEventListener('my_event', onMyEvent);
    } 
  },[var1])

  const doSomething = ()=>{
    document.dispatchEvent(new CustomEvent('my_event', { detail: 5 }));
  }

  return (
    <div className="App">
      <div>{var1.toString()}</div><br/>
      <button onClick={doSomething}>Show Data</button>
    </div>
  );
}

export default App;

Upvotes: 1

Shripada
Shripada

Reputation: 6524

Fix is given by Arthur. Let me discuss the insight into why your code wont work is a classical issue of closures closing over their context. In your case what happens is the when the component App renders for first time the closure:

  const onMyEvent = (e)=>{
    console.log("Detail var1 in onMyEvent: ", var1)//var1 value still prints as [] and not ['a','b']
  }

closes over the var1, and at this point, the initial value of var1 is still empty array []. Remember the useEffect with an empty dependency will cause it to run only for first time, and it will end up setting the closure created during first render as the callback. Since closures will carry the same values they closed over during their creation, whenever they are invoked, they will still have access to the values they had closed over. In your case it is empty array for var1.

Now, as Arthur Costa, pointed out the solution is to add var1 as the dependency to useEffect. This will ensure, each time var1 changes the effect will be called, and you will end up setting a new closure with new value of var1 closed over. And this is why it works when we add the right dependency. As a best practice, we need to ensure to add dependencies that are being used within useEffect. Detailed documentation is here: https://reactjs.org/docs/hooks-effect.html

Upvotes: 1

Related Questions