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!