Skip to content

Commit 97fd3e7

Browse files
authored
Ensure useState and useReducer initializer functions are double invoked in StrictMode (#28248)
1 parent 08d6cef commit 97fd3e7

File tree

2 files changed

+53
-1
lines changed

2 files changed

+53
-1
lines changed

packages/react-reconciler/src/ReactFiberHooks.js

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1154,6 +1154,11 @@ function mountReducer<S, I, A>(
11541154
let initialState;
11551155
if (init !== undefined) {
11561156
initialState = init(initialArg);
1157+
if (shouldDoubleInvokeUserFnsInHooksDEV) {
1158+
setIsStrictModeForDevtools(true);
1159+
init(initialArg);
1160+
setIsStrictModeForDevtools(false);
1161+
}
11571162
} else {
11581163
initialState = ((initialArg: any): S);
11591164
}
@@ -1745,8 +1750,15 @@ function forceStoreRerender(fiber: Fiber) {
17451750
function mountStateImpl<S>(initialState: (() => S) | S): Hook {
17461751
const hook = mountWorkInProgressHook();
17471752
if (typeof initialState === 'function') {
1753+
const initialStateInitializer = initialState;
17481754
// $FlowFixMe[incompatible-use]: Flow doesn't like mixed types
1749-
initialState = initialState();
1755+
initialState = initialStateInitializer();
1756+
if (shouldDoubleInvokeUserFnsInHooksDEV) {
1757+
setIsStrictModeForDevtools(true);
1758+
// $FlowFixMe[incompatible-use]: Flow doesn't like mixed types
1759+
initialStateInitializer();
1760+
setIsStrictModeForDevtools(false);
1761+
}
17501762
}
17511763
hook.memoizedState = hook.baseState = initialState;
17521764
const queue: UpdateQueue<S, BasicStateAction<S>> = {

packages/react/src/__tests__/ReactStrictMode-test.js

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,46 @@ describe('ReactStrictMode', () => {
202202
expect(instance.state.count).toBe(2);
203203
});
204204

205+
// @gate debugRenderPhaseSideEffectsForStrictMode
206+
it('double invokes useState and useReducer initializers functions', async () => {
207+
const log = [];
208+
209+
function App() {
210+
React.useState(() => {
211+
log.push('Compute initial state count: 1');
212+
return 1;
213+
});
214+
React.useReducer(
215+
s => s,
216+
2,
217+
s => {
218+
log.push('Compute initial reducer count: 2');
219+
return s;
220+
},
221+
);
222+
223+
return 3;
224+
}
225+
226+
const container = document.createElement('div');
227+
const root = ReactDOMClient.createRoot(container);
228+
await act(() => {
229+
root.render(
230+
<React.StrictMode>
231+
<App />
232+
</React.StrictMode>,
233+
);
234+
});
235+
expect(container.textContent).toBe('3');
236+
237+
expect(log).toEqual([
238+
'Compute initial state count: 1',
239+
'Compute initial state count: 1',
240+
'Compute initial reducer count: 2',
241+
'Compute initial reducer count: 2',
242+
]);
243+
});
244+
205245
it('should invoke only precommit lifecycle methods twice in DEV legacy roots', async () => {
206246
const {StrictMode} = React;
207247

0 commit comments

Comments
 (0)