Skip to content

Commit 5450dd4

Browse files
authored
Strict Mode: Reuse memoized result from first pass (#25583)
In Strict Mode, during development, user functions are double invoked to help detect side effects. Currently, the way we implement this is to completely discard the first pass and start over. Theoretically this should be fine because components are idempotent. However, it's a bit tricky to get right because our implementation (i.e. `renderWithHooks`) is not completely idempotent with respect to internal data structures, like the work-in-progress fiber. In the past we've had to be really careful to avoid subtle bugs — for example, during the initial mount, `setState` functions are bound to the particular hook instances that were created during that render. If we compute new hook instances, we must also compute new children, and they must correspond to each other. This commit addresses a similar issue that came up related to `use`: when something suspends, `use` reuses the promise that was passed during the first attempt. This is itself a form of memoization. We need to be able to memoize the reactive inputs to the `use` call using a hook (i.e. `useMemo`), which means, the reactive inputs to `use` must come from the same component invocation as the output. The solution I've chosen is, rather than double invoke the entire `renderWithHook` function, we should double invoke each individual user function. It's a bit confusing but here's how it works: We will invoke the entire component function twice. However, during the second invocation of the component, the hook state from the first invocation will be reused. That means things like `useMemo` functions won't run again, because the deps will match and the memoized result will be reused. We want memoized functions to run twice, too, so account for this, user functions are double invoked during the *first* invocation of the component function, and are *not* double invoked during the second incovation: - First execution of component function: user functions are double invoked - Second execution of component function (in Strict Mode, during development): user functions are not double invoked. It's hard to explain verbally but much clearer when you run the test cases I've added.
1 parent d2a0176 commit 5450dd4

File tree

5 files changed

+455
-198
lines changed

5 files changed

+455
-198
lines changed

packages/react-reconciler/src/ReactFiberBeginWork.new.js

-58
Original file line numberDiff line numberDiff line change
@@ -418,25 +418,6 @@ function updateForwardRef(
418418
renderLanes,
419419
);
420420
hasId = checkDidRenderIdHook();
421-
if (
422-
debugRenderPhaseSideEffectsForStrictMode &&
423-
workInProgress.mode & StrictLegacyMode
424-
) {
425-
setIsStrictModeForDevtools(true);
426-
try {
427-
nextChildren = renderWithHooks(
428-
current,
429-
workInProgress,
430-
render,
431-
nextProps,
432-
ref,
433-
renderLanes,
434-
);
435-
hasId = checkDidRenderIdHook();
436-
} finally {
437-
setIsStrictModeForDevtools(false);
438-
}
439-
}
440421
setIsRendering(false);
441422
} else {
442423
nextChildren = renderWithHooks(
@@ -1125,25 +1106,6 @@ function updateFunctionComponent(
11251106
renderLanes,
11261107
);
11271108
hasId = checkDidRenderIdHook();
1128-
if (
1129-
debugRenderPhaseSideEffectsForStrictMode &&
1130-
workInProgress.mode & StrictLegacyMode
1131-
) {
1132-
setIsStrictModeForDevtools(true);
1133-
try {
1134-
nextChildren = renderWithHooks(
1135-
current,
1136-
workInProgress,
1137-
Component,
1138-
nextProps,
1139-
context,
1140-
renderLanes,
1141-
);
1142-
hasId = checkDidRenderIdHook();
1143-
} finally {
1144-
setIsStrictModeForDevtools(false);
1145-
}
1146-
}
11471109
setIsRendering(false);
11481110
} else {
11491111
nextChildren = renderWithHooks(
@@ -1969,26 +1931,6 @@ function mountIndeterminateComponent(
19691931
getComponentNameFromType(Component) || 'Unknown',
19701932
);
19711933
}
1972-
1973-
if (
1974-
debugRenderPhaseSideEffectsForStrictMode &&
1975-
workInProgress.mode & StrictLegacyMode
1976-
) {
1977-
setIsStrictModeForDevtools(true);
1978-
try {
1979-
value = renderWithHooks(
1980-
null,
1981-
workInProgress,
1982-
Component,
1983-
props,
1984-
context,
1985-
renderLanes,
1986-
);
1987-
hasId = checkDidRenderIdHook();
1988-
} finally {
1989-
setIsStrictModeForDevtools(false);
1990-
}
1991-
}
19921934
}
19931935

19941936
if (getIsHydrating() && hasId) {

packages/react-reconciler/src/ReactFiberBeginWork.old.js

-58
Original file line numberDiff line numberDiff line change
@@ -418,25 +418,6 @@ function updateForwardRef(
418418
renderLanes,
419419
);
420420
hasId = checkDidRenderIdHook();
421-
if (
422-
debugRenderPhaseSideEffectsForStrictMode &&
423-
workInProgress.mode & StrictLegacyMode
424-
) {
425-
setIsStrictModeForDevtools(true);
426-
try {
427-
nextChildren = renderWithHooks(
428-
current,
429-
workInProgress,
430-
render,
431-
nextProps,
432-
ref,
433-
renderLanes,
434-
);
435-
hasId = checkDidRenderIdHook();
436-
} finally {
437-
setIsStrictModeForDevtools(false);
438-
}
439-
}
440421
setIsRendering(false);
441422
} else {
442423
nextChildren = renderWithHooks(
@@ -1125,25 +1106,6 @@ function updateFunctionComponent(
11251106
renderLanes,
11261107
);
11271108
hasId = checkDidRenderIdHook();
1128-
if (
1129-
debugRenderPhaseSideEffectsForStrictMode &&
1130-
workInProgress.mode & StrictLegacyMode
1131-
) {
1132-
setIsStrictModeForDevtools(true);
1133-
try {
1134-
nextChildren = renderWithHooks(
1135-
current,
1136-
workInProgress,
1137-
Component,
1138-
nextProps,
1139-
context,
1140-
renderLanes,
1141-
);
1142-
hasId = checkDidRenderIdHook();
1143-
} finally {
1144-
setIsStrictModeForDevtools(false);
1145-
}
1146-
}
11471109
setIsRendering(false);
11481110
} else {
11491111
nextChildren = renderWithHooks(
@@ -1969,26 +1931,6 @@ function mountIndeterminateComponent(
19691931
getComponentNameFromType(Component) || 'Unknown',
19701932
);
19711933
}
1972-
1973-
if (
1974-
debugRenderPhaseSideEffectsForStrictMode &&
1975-
workInProgress.mode & StrictLegacyMode
1976-
) {
1977-
setIsStrictModeForDevtools(true);
1978-
try {
1979-
value = renderWithHooks(
1980-
null,
1981-
workInProgress,
1982-
Component,
1983-
props,
1984-
context,
1985-
renderLanes,
1986-
);
1987-
hasId = checkDidRenderIdHook();
1988-
} finally {
1989-
setIsStrictModeForDevtools(false);
1990-
}
1991-
}
19921934
}
19931935

19941936
if (getIsHydrating() && hasId) {

0 commit comments

Comments
 (0)