Skip to content

Commit

Permalink
Ensure useState and useReducer initializer functions are double invok…
Browse files Browse the repository at this point in the history
…ed in StrictMode (#28248)
  • Loading branch information
eps1lon authored Feb 6, 2024
1 parent 08d6cef commit 97fd3e7
Show file tree
Hide file tree
Showing 2 changed files with 53 additions and 1 deletion.
14 changes: 13 additions & 1 deletion packages/react-reconciler/src/ReactFiberHooks.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down Expand Up @@ -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>> = {
Expand Down
40 changes: 40 additions & 0 deletions packages/react/src/__tests__/ReactStrictMode-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down

0 comments on commit 97fd3e7

Please sign in to comment.