@samitouri / QOS-React-1 / commits / 97fd3e7064

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