In my application, I'm using a dispatch from useReducer hook on click of a button and in the same function I'm using a setTimeout of 2 seconds. But when I store the data using dispatch of usereducer then I'm not getting updated value in setTimeout function.
I cannot share original code, but sharing a snippet of another demo app where this issue occurs.
const initialData = { data: "ABC" };
function reducer(state = initialData, action) {
switch (action.type) {
case "STORE":
return {
...state,
data: action.payload
};
default:
return state;
break;
}
}
function Demo() {
const [state, dispatch] = React.useReducer(reducer, initialData);
console.log("Render : ",state.data); //Will be executed on each rendering
const handleClick = () => {
dispatch({
type: "STORE",
payload: state.data + parseInt(Math.random() * 10)
});
setTimeout(() => {
console.log("ButtonClick : ",state.data); //Will be executed after 2 seconds of dispatching.
}, 2000);
};
return <button onClick={handleClick}>{state.data}</button>;
}
ReactDOM.render(<Demo />, document.getElementById("app"));
In above example I'm storing data in reducer using dispatch, and I'm calling console.log("ButtonClick") on Button Click after 2 seconds but even after 2 seconds, I'm not getting updated data in console. But in console.log("Render") I'm getting updated data.
Live example on : https://codepen.io/aadi-git/pen/yLJLmNa
When you call
this is what happens:
Run
dispatchwith an object to store some data. This function is executed asynchronically, so the result is not available immediately.Register a timeout handler, which logs the current value of
state.datato the console. Since the precedingdispatchis still working in progress, the value ofstate.datais still the old one.This means you can not log a new dispatched value by running
console.logafter yourdispatchcall because you can not see into the future. You can only log the new data after a re-render of your component due to changing state. Then you can and should useSome more explanations about
setTimeoutand whyconsole.loglogs old values within itYou use
and this is equivalent to
In the first line you create a function (named
callbackhere), which prints some text. This text consists of a simple string and the value ofstate.data. Therefore this function has a reference to the variablestate.data. The function in combination with the reference to the outer state is called closure and this closure ensures, that the valuestate.datais kept alive as long as the function lives (is not binned by the garbage collector).In the second line
setTimeoutis called with this function. Simplified this means a task is stored somewhere which states, that exactly this function has to be executed after the given timeout. So as long as this task is not done, the function stays alive and with it the variablestate.data.In the meantime long before the task is handled, the new action is
dispatched, new state calculated andDemore-rendered. With that a newstate.dataand a newhandleClickis created. With the new creation ofhandleClickalso a new function is created which is passed tosetTimeout. This wholehandleClickfunction is then passed asonClickhandler for thebuttonelement.The re-render is now over, but the task from before is still pending. Now when the timeout duration ends (long after re-rendering the component) the task is taken from the task queue and executed. The task still has reference to the function from the render before and this function still has reference to the value
state.datafrom the render before. So the value logged to the console is still the old value from the render before.