Ensure useState and useReducer initializer functions are double invoked in StrictMode (#28248)
Sebastian Silbermann committed
Feb 6, 2024 at 17:53 UTC
97fd3e7064b162f05b1bac3962ed10c6559c346c
2 files changed
+53
-1
packages/react-reconciler/src/ReactFiberHooks.js
+13
-1
@@ -1154,6 +1154,11 @@ function mountReducer<S, I, A>(
1154
let initialState;
1155
if (init !== undefined) {
1156
initialState = init(initialArg);
1157
+ if (shouldDoubleInvokeUserFnsInHooksDEV) {
1158
+ setIsStrictModeForDevtools(true);
1159
+ init(initialArg);
1160
+ setIsStrictModeForDevtools(false);
1161
+ }
1162
} else {
1163
initialState = ((initialArg: any): S);
1164
}
@@ -1745,8 +1750,15 @@ function forceStoreRerender(fiber: Fiber) {
1750
function mountStateImpl<S>(initialState: (() => S) | S): Hook {
1751
const hook = mountWorkInProgressHook();
1752
if (typeof initialState === 'function') {
1753
+ const initialStateInitializer = initialState;
1754
// $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
+ }
1762
}
1763
hook.memoizedState = hook.baseState = initialState;
1764
const queue: UpdateQueue<S, BasicStateAction<S>> = {
packages/react/src/__tests__/ReactStrictMode-test.js
+40
@@ -202,6 +202,46 @@ describe('ReactStrictMode', () => {
202
expect(instance.state.count).toBe(2);
203
});
204
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
+
245
it('should invoke only precommit lifecycle methods twice in DEV legacy roots', async () => {
246
const {StrictMode} = React;
247