Because of the asynchronous nature of React setState
and the newly introduced react concurrent mode after a lot of research, I don't know how I can access the guaranteed latest state in the following scenario or any other scenario.
An answer from a react expert especially from the React team is appreciated so much.
Please remember it's a so simplified example and the real problem may occur in a sophisticated project full of state updates, event handlers, async code that changes state, ...
import { useCallback, useState } from "react";
const Example = ({ onIncrement }) => {
const [count, setCount] = useState(0);
const increment = useCallback(() => {
onIncrement(count, count + 1); // Is count guaranteed to be the latest state here due to including count in the useCallback dependency array?
setCount((count) => count + 1);
}, [count, onIncrement]);
return (
<>
<span>{count}</span>
<button onClick={increment}>increment</button>
</>
);
};
const Parent = () => (
<Example
onIncrement={(currentCount, incrementedCount) =>
console.log(
`count before incrementing: ${currentCount}, after increment: ${incrementedCount}`
)
}
/>
);
export default Parent;
You may say I could call onIncrement
in the setCount
callback, but due to the new react concurrent mode you can see that in future react updates the onIncrement
may be called twice and output the result twice which is not the desired result.
The commit phase is usually very fast, but rendering can be slow. For this reason, the upcoming concurrent mode (which is not enabled by default yet) breaks the rendering work into pieces, pausing and resuming the work to avoid blocking the browser. This means that React may invoke render phase lifecycles more than once before committing, or it may invoke them without committing at all (because of an error or a higher priority interruption).
You can already (React 17 strict mode) see that onIncrement
will be called twice in development mode and in the feature after React concurrent mode becomes the default, so it may be called twice this way in production.
Does including count in the useCallback
dependencies array guaranty that count is the latest state value?
You may suggest calling the onIncrement
in a useEffect
hook but that way I will not have access to the previous state, which unlike this example may be impossible to recalculate. (using a ref for storing the previous state is not a solution)
Another problem with using useEffect
is that I don't know for sure that this event handler (onIncrement) is the cause of the effect or has state change in another handler or useEffect
callback caused the effect. (storing an extra state or ref for detecting the cause is overkill)
Thank you!