diff --git a/packages/react-dom/src/__tests__/ReactDOMServerIntegrationHooks-test.internal.js b/packages/react-dom/src/__tests__/ReactDOMServerIntegrationHooks-test.internal.js index 97de8867b2b..12a3defdf25 100644 --- a/packages/react-dom/src/__tests__/ReactDOMServerIntegrationHooks-test.internal.js +++ b/packages/react-dom/src/__tests__/ReactDOMServerIntegrationHooks-test.internal.js @@ -209,6 +209,23 @@ describe('ReactDOMServerHooks', () => { expect(domNode.textContent).toEqual('0'); }); + itRenders('with default reducer state value', async render => { + function reducer(state = 0, action) { + return action === 'increment' ? state + 1 : state; + } + function Counter() { + let [count] = useReducer(reducer); + yieldValue('Render: ' + count); + return ; + } + + const domNode = await render(); + + expect(clearYields()).toEqual(['Render: 0', 0]); + expect(domNode.tagName).toEqual('SPAN'); + expect(domNode.textContent).toEqual('0'); + }); + itRenders('lazy initialization with initialAction', async render => { function reducer(state, action) { return action === 'increment' ? state + 1 : state; diff --git a/packages/react-dom/src/server/ReactPartialRendererHooks.js b/packages/react-dom/src/server/ReactPartialRendererHooks.js index 025b51a8c02..acae8d4b845 100644 --- a/packages/react-dom/src/server/ReactPartialRendererHooks.js +++ b/packages/react-dom/src/server/ReactPartialRendererHooks.js @@ -225,7 +225,7 @@ export function useState( } export function useReducer( - reducer: (S, A) => S, + reducer: (S, A | any) => S, initialState: S, initialAction: A | void | null, ): [S, Dispatch] { @@ -277,7 +277,12 @@ export function useReducer( initialState = reducer(initialState, initialAction); } currentlyRenderingComponent = component; - workInProgressHook.memoizedState = initialState; + if (initialState === undefined) { + // use default reducer state value + workInProgressHook.memoizedState = reducer(initialState, {}); + } else { + workInProgressHook.memoizedState = initialState; + } const queue: UpdateQueue = (workInProgressHook.queue = { last: null, dispatch: null, diff --git a/packages/react-reconciler/src/ReactFiberHooks.js b/packages/react-reconciler/src/ReactFiberHooks.js index 4af03961690..f4a6400d3fc 100644 --- a/packages/react-reconciler/src/ReactFiberHooks.js +++ b/packages/react-reconciler/src/ReactFiberHooks.js @@ -411,7 +411,7 @@ export function useState( } export function useReducer( - reducer: (S, A) => S, + reducer: (S, A | any) => S, initialState: S, initialAction: A | void | null, ): [S, Dispatch] { @@ -557,7 +557,15 @@ export function useReducer( initialState = reducer(initialState, initialAction); } currentlyRenderingFiber = fiber; - workInProgressHook.memoizedState = workInProgressHook.baseState = initialState; + if (initialState === undefined) { + // use default reducer state value + workInProgressHook.memoizedState = workInProgressHook.baseState = reducer( + initialState, + {}, + ); + } else { + workInProgressHook.memoizedState = workInProgressHook.baseState = initialState; + } queue = workInProgressHook.queue = { last: null, dispatch: null, diff --git a/packages/react-reconciler/src/__tests__/ReactHooksWithNoopRenderer-test.internal.js b/packages/react-reconciler/src/__tests__/ReactHooksWithNoopRenderer-test.internal.js index 74d69fac78d..3ebdb105df1 100644 --- a/packages/react-reconciler/src/__tests__/ReactHooksWithNoopRenderer-test.internal.js +++ b/packages/react-reconciler/src/__tests__/ReactHooksWithNoopRenderer-test.internal.js @@ -525,6 +525,44 @@ describe('ReactHooksWithNoopRenderer', () => { expect(ReactNoop.getChildren()).toEqual([span('Count: -2')]); }); + it('accepts default reducer state value as initial state', () => { + const INCREMENT = 'INCREMENT'; + const DECREMENT = 'DECREMENT'; + + function reducer(state = 0, action) { + switch (action) { + case 'INCREMENT': + return state + 1; + case 'DECREMENT': + return state - 1; + default: + return state; + } + } + + function Counter(props, ref) { + const [count, dispatch] = useReducer(reducer); + useImperativeHandle(ref, () => ({dispatch})); + return ; + } + + Counter = forwardRef(Counter); + const counter = React.createRef(null); + ReactNoop.render(); + ReactNoop.flush(); + expect(ReactNoop.getChildren()).toEqual([span('Count: 0')]); + + counter.current.dispatch(INCREMENT); + ReactNoop.flush(); + expect(ReactNoop.getChildren()).toEqual([span('Count: 1')]); + + counter.current.dispatch(DECREMENT); + counter.current.dispatch(DECREMENT); + counter.current.dispatch(DECREMENT); + ReactNoop.flush(); + expect(ReactNoop.getChildren()).toEqual([span('Count: -2')]); + }); + it('accepts an initial action', () => { const INCREMENT = 'INCREMENT'; const DECREMENT = 'DECREMENT';