Open bthall16 opened 2 months ago
maybe this can help you. Not only is there double rendering at the component level, but it also occurs at the reducer level.
It's not a solution to the problem, but maybe with the context you already have you can see something that I don't see, good luck. I'll review it in more detail tomorrow.
maybe this can help you. Not only is there double rendering at the component level, but it also occurs at the reducer level.
With <StrictMode />
one of the reducer's results should be thrown away which is what appears to be happening with the <UpdateEffectDispatch />
but not <MountEffectDispatch />
.
If, for example, I had a reducer that just increments by 1:
function MountCounter() {
const [value, dispatch] = useReducer((x) => {
console.log("[Reducer value]:", x);
return x + 1;
}, 0);
useEffect(() => {
dispatch();
}, []);
console.log("[Render value]:", value);
return null;
}
function UpdateCounter() {
const [value, dispatch] = useReducer((x) => {
console.log("[Reducer value]:", x);
return x + 1;
}, 0);
const [mounted, setMounted] = useState(false);
useEffect(() => {
setMounted(true);
}, []);
useEffect(() => {
if (!mounted) {
return;
}
dispatch();
}, [mounted]);
console.log("[Render value]:", value);
return null;
}
<MountCounter />
logs (blank lines added for clarity):
[Render value]: – 0
[Render value]: – 0
[Reducer value]: – 0
[Reducer value]: – 1
[Render value]: – 2
[Reducer value]: – 0
[Reducer value]: – 1
[Render value]: – 2
<UpdateCounter />
logs (blank lines added for clarity):
[Render value]: – 0
[Render value]: – 0
[Render value]: – 0
[Render value]: – 0
[Reducer value]: – 0
[Render value]: – 1
[Reducer value]: – 0
[Render value]: – 1
I'm not sure which sequence of logs is "correct" here but the final result of <UpdateCounter />
is what I'd expect to see: the value 1
is logged, not 2
(which is what we see from <MountCounter />
).
What's interesting is that <MountCounter />
appears to show the component rendering twice and the reducer running twice per individual render, whereas <UpdateCounter />
appears to show the component rendering twice but the reducer running once per render. Further, <MountCounter />
appears to be using the result from the first reducer call to pass to the second reducer call within an individual render.
built an app to get paid for this PR https://www.n0va-io.com/discover/facebook/react
When rendering an app using
<StrictMode />
, calling a reducer'sdispatch
function in an Effect appears to have different observable behaviors depending on whether the Effect is mounting or updating.React version: 18.3.1
Steps to reproduce:
<StrictMode />
.Minimal reproduction:
In this reproduction, both components call
useReducer
with a reducer function containing invariants, e.g. to prevent invalid states. For this reproduction, the reducer toggles a boolean value totrue
once and throws an error for any future dispatches.Each component then calls
useReducer's
dispatch
function in an Effect. The reducer's invariant is only violated in<MountEffectDispatch />
, not<UpdateEffectDispatch />
even though both are being double-invoked as part of<StrictMode />
.This specific way of surfacing the different behaviors between mount and update Effects is simplified from an app I'm developing so it may obfuscate the underlying issue (if there is one).
This behavior isn't seen outside of
<StrictMode />
.The current behavior
Reducer state updates in mount Effects behave differently from reducer state updates in update Effects when using
<StrictMode />
.The expected behavior
Reducer state updates should behave consistently in any Effect when using
<StrictMode />
.