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
dispatch
with 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.data
to the console. Since the precedingdispatch
is still working in progress, the value ofstate.data
is still the old one.This means you can not log a new dispatched value by running
console.log
after yourdispatch
call 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
setTimeout
and whyconsole.log
logs old values within itYou use
and this is equivalent to
In the first line you create a function (named
callback
here), 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.data
is kept alive as long as the function lives (is not binned by the garbage collector).In the second line
setTimeout
is 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
dispatch
ed, new state calculated andDemo
re-rendered. With that a newstate.data
and a newhandleClick
is created. With the new creation ofhandleClick
also a new function is created which is passed tosetTimeout
. This wholehandleClick
function is then passed asonClick
handler for thebutton
element.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.data
from the render before. So the value logged to the console is still the old value from the render before.