From 8c4cd65cfaa4614bac7fd7783b4ec502a337eba3 Mon Sep 17 00:00:00 2001 From: Andrew Clark Date: Thu, 24 Feb 2022 10:57:37 -0500 Subject: [PATCH] Add warnings for common root API mistakes (#23356) For createRoot, a common mistake is to pass JSX as the second argument, instead of calling root.render. For hydrateRoot, a common mistake is to forget to pass children as the second argument. The type system will enforce correct usage, but since not everyone uses types we'll log a helpful warning, too. --- .../src/__tests__/ReactDOMRoot-test.js | 23 ++++++++++++++++++ packages/react-dom/src/client/ReactDOMRoot.js | 24 +++++++++++++++++++ 2 files changed, 47 insertions(+) diff --git a/packages/react-dom/src/__tests__/ReactDOMRoot-test.js b/packages/react-dom/src/__tests__/ReactDOMRoot-test.js index 40c9666835..c673423f2b 100644 --- a/packages/react-dom/src/__tests__/ReactDOMRoot-test.js +++ b/packages/react-dom/src/__tests__/ReactDOMRoot-test.js @@ -420,4 +420,27 @@ describe('ReactDOMRoot', () => { // Still works in the legacy API ReactDOM.render(
, commentNode); }); + + it('warn if no children passed to hydrateRoot', async () => { + expect(() => + ReactDOM.hydrateRoot(container), + ).toErrorDev( + 'Must provide initial children as second argument to hydrateRoot.', + {withoutStack: true}, + ); + }); + + it('warn if JSX passed to createRoot', async () => { + function App() { + return 'Child'; + } + + expect(() => ReactDOM.createRoot(container, )).toErrorDev( + 'You passed a JSX element to createRoot. You probably meant to call ' + + 'root.render instead', + { + withoutStack: true, + }, + ); + }); }); diff --git a/packages/react-dom/src/client/ReactDOMRoot.js b/packages/react-dom/src/client/ReactDOMRoot.js index 9665cf07f9..46786fcdea 100644 --- a/packages/react-dom/src/client/ReactDOMRoot.js +++ b/packages/react-dom/src/client/ReactDOMRoot.js @@ -15,6 +15,7 @@ import type { } from 'react-reconciler/src/ReactInternalTypes'; import {queueExplicitHydrationTarget} from '../events/ReactDOMEventReplaying'; +import {REACT_ELEMENT_TYPE} from 'shared/ReactSymbols'; export type RootType = { render(children: ReactNodeList): void, @@ -174,6 +175,20 @@ export function createRoot( console.warn( 'hydrate through createRoot is deprecated. Use ReactDOM.hydrateRoot(container, ) instead.', ); + } else { + if ( + typeof options === 'object' && + options !== null && + (options: any).$$typeof === REACT_ELEMENT_TYPE + ) { + console.error( + 'You passed a JSX element to createRoot. You probably meant to ' + + 'call root.render instead. ' + + 'Example usage:\n\n' + + ' let root = createRoot(domContainer);\n' + + ' root.render();', + ); + } } } if (options.unstable_strictMode === true) { @@ -237,6 +252,15 @@ export function hydrateRoot( warnIfReactDOMContainerInDEV(container); + if (__DEV__) { + if (initialChildren === undefined) { + console.error( + 'Must provide initial children as second argument to hydrateRoot. ' + + 'Example usage: hydrateRoot(domContainer, )', + ); + } + } + // For now we reuse the whole bag of options since they contain // the hydration callbacks. const hydrationCallbacks = options != null ? options : null;