diff --git a/packages/react-reconciler/src/ReactFiberHooks.js b/packages/react-reconciler/src/ReactFiberHooks.js index 29c83c7d7263..ca1500ab42aa 100644 --- a/packages/react-reconciler/src/ReactFiberHooks.js +++ b/packages/react-reconciler/src/ReactFiberHooks.js @@ -2917,6 +2917,15 @@ function mountMemo( nextCreate: () => T, deps: Array | void | null, ): T { + if (__DEV__) { + if (typeof nextCreate !== 'function') { + console.error( + 'Expected useMemo() first argument to be a function that returns a value. ' + + 'Instead received: %s.', + nextCreate !== null ? typeof nextCreate : 'null', + ); + } + } const hook = mountWorkInProgressHook(); const nextDeps = deps === undefined ? null : deps; const nextValue = nextCreate(); @@ -2936,6 +2945,15 @@ function updateMemo( nextCreate: () => T, deps: Array | void | null, ): T { + if (__DEV__) { + if (typeof nextCreate !== 'function') { + console.error( + 'Expected useMemo() first argument to be a function that returns a value. ' + + 'Instead received: %s.', + nextCreate !== null ? typeof nextCreate : 'null', + ); + } + } const hook = updateWorkInProgressHook(); const nextDeps = deps === undefined ? null : deps; const prevState = hook.memoizedState; diff --git a/packages/react-reconciler/src/__tests__/ReactHooks-test.internal.js b/packages/react-reconciler/src/__tests__/ReactHooks-test.internal.js index e61e4a825602..2f8019375814 100644 --- a/packages/react-reconciler/src/__tests__/ReactHooks-test.internal.js +++ b/packages/react-reconciler/src/__tests__/ReactHooks-test.internal.js @@ -858,6 +858,33 @@ describe('ReactHooks', () => { ]); }); + // https://github.com/facebook/react/issues/16589 + it('warns when useMemo is called with a non-function first argument', async () => { + const {useMemo} = React; + function App() { + // $FlowExpectedError[incompatible-call] Testing runtime behaviour. + useMemo({value: 1}, []); + return null; + } + App.displayName = 'App'; + + await expect(async () => { + await act(() => { + ReactTestRenderer.create(, {unstable_isConcurrent: true}); + }); + }).rejects.toThrow('nextCreate is not a function'); + // The warning fires twice because concurrent mode retries the render + // after the TypeError thrown when `nextCreate` is invoked. + assertConsoleErrorDev([ + 'Expected useMemo() first argument to be a function that returns a value. ' + + 'Instead received: object.\n' + + ' in App (at **)', + 'Expected useMemo() first argument to be a function that returns a value. ' + + 'Instead received: object.\n' + + ' in App (at **)', + ]); + }); + // https://github.com/facebook/react/issues/14022 it('works with ReactDOMServer calls inside a component', async () => { const {useState} = React;