Repository: facebook/react
Pull request: Ensure useState and useReducer initializer functions are double invoked in StrictMode


## Summary

Fixes https://github.com/facebook/react/pull/25583#issuecomment-1899170319

In StrictMode we used to double invoke state initializer functions in StrictMode (I started bisecting from 17 where this was already the case). Our docs imply this:
> Functions that you pass to [useState](https://react.dev/reference/react/useState), [set functions](https://react.dev/reference/react/useState#setstate), [useMemo](https://react.dev/reference/react/useMemo), or [useReducer](https://react.dev/reference/react/useReducer)
-- https://react.dev/reference/react/StrictMode#fixing-bugs-found-by-double-rendering-in-development

However, in https://github.com/facebook/react/pull/25583 we stopped doing that but likely because we never had a test. 

Now we double invoke again using the same pattern we use for double invoking updater functions.

## How did you test this change?

- added test for state initializer invocations


Changed files:
filename: packages/react-reconciler/src/ReactFiberHooks.js
patch: @@ -1154,6 +1154,11 @@ function mountReducer<S, I, A>(
   let initialState;
   if (init !== undefined) {
     initialState = init(initialArg);
+    if (shouldDoubleInvokeUserFnsInHooksDEV) {
+      setIsStrictModeForDevtools(true);
+      init(initialArg);
+      setIsStrictModeForDevtools(false);
+    }
   } else {
     initialState = ((initialArg: any): S);
   }
@@ -1745,8 +1750,15 @@ function forceStoreRerender(fiber: Fiber) {
 function mountStateImpl<S>(initialState: (() => S) | S): Hook {
   const hook = mountWorkInProgressHook();
   if (typeof initialState === 'function') {
+    const initialStateInitializer = initialState;
     // $FlowFixMe[incompatible-use]: Flow doesn't like mixed types
-    initialState = initialState();
+    initialState = initialStateInitializer();
+    if (shouldDoubleInvokeUserFnsInHooksDEV) {
+      setIsStrictModeForDevtools(true);
+      // $FlowFixMe[incompatible-use]: Flow doesn't like mixed types
+      initialStateInitializer();
+      setIsStrictModeForDevtools(false);
+    }
   }
   hook.memoizedState = hook.baseState = initialState;
   const queue: UpdateQueue<S, BasicStateAction<S>> = {

filename: packages/react/src/__tests__/ReactStrictMode-test.js
patch: @@ -202,6 +202,46 @@ describe('ReactStrictMode', () => {
     expect(instance.state.count).toBe(2);
   });
 
+  // @gate debugRenderPhaseSideEffectsForStrictMode
+  it('double invokes useState and useReducer initializers functions', async () => {
+    const log = [];
+
+    function App() {
+      React.useState(() => {
+        log.push('Compute initial state count: 1');
+        return 1;
+      });
+      React.useReducer(
+        s => s,
+        2,
+        s => {
+          log.push('Compute initial reducer count: 2');
+          return s;
+        },
+      );
+
+      return 3;
+    }
+
+    const container = document.createElement('div');
+    const root = ReactDOMClient.createRoot(container);
+    await act(() => {
+      root.render(
+        <React.StrictMode>
+          <App />
+        </React.StrictMode>,
+      );
+    });
+    expect(container.textContent).toBe('3');
+
+    expect(log).toEqual([
+      'Compute initial state count: 1',
+      'Compute initial state count: 1',
+      'Compute initial reducer count: 2',
+      'Compute initial reducer count: 2',
+    ]);
+  });
+
   it('should invoke only precommit lifecycle methods twice in DEV legacy roots', async () => {
     const {StrictMode} = React;