I am using Redux Saga along with Redux Toolkit. I have dispatched an action and need its result in a setTimeout
. But once I have dispatched the action the code execution continues and when the setTimeout runs, I do not get the updated result instead I get the results from previously dispatched action (I dispatch the same action when the component mounts).
File.js
const {neededData} = useSelector(state => state.something) // property from redux store
useEffect(() => {
dispatch(Slice.actions.getHealthDataBetweenDates(params));
}, [])
function someFunction() {
dispatch(Slice.actions.getHealthDataBetweenDates(params));
console.log(neededData) // logs an array of objects, but the it is not updated data
setTimeout(() => {
console.log(neededData) // logs an array of objects, but it is not updated data
}, 1000)
}
Saga.js
function* getHealthDataBetweenDates({ payload }) {
try {
const result = yield call(ProfileApi.getHealthDataBetweenDates, payload)
if (result.status == true) {
yield put(Slice.actions.getHealthDataBetweenDatesSuccess(result.data));
console.log('saga minutes', result.data.data) // returns array of object, with updated data
}else {
yield put(Slice.actions.getHealthDataBetweenDatesFailure());
}
} catch (error) {
console.log('error: ' error)
}
}
When the function executes, I first get the log right below the dispatch statement, then the log from saga and then the log from setTimeout.
CodePudding user response:
When you run setTimeout
, you create a closure around the function you pass in. Variables are "frozen" at the time the closure is created. React will still re-render your component when your saga updates your store, but the values in setTimeout
have already been closed. The MDN spec on closures is well-written and very helpful.
React offers an "escape hatch" from these restrictions: refs. Refs exist outside the render cycle of the component and its closures. You can wire a ref to your store with useEffect to get the behavior you're looking for.
Here's a minimal example:
// let's say the selector returns 0
const data = useSelector(state => state.data);
const ref = useRef(data);
// update the ref every time your selector changes
useEffect(() => {
ref.current = data;
}, [data]);
// start timeouts
useEffect(() => {
// set data at 500ms
setTimeout(() => dispatch(Slice.actions.setData(1)), 500);
// log data at 1000ms
setTimeout(() => console.log({ data: ref.current }), 1000);
}, []);
This will log { data: 1 }
to the console after ~1000ms.