Skip to content

Commit

Permalink
Add warnings for common root API mistakes (#23356)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
acdlite committed Feb 24, 2022
1 parent a5b2215 commit 8c4cd65
Show file tree
Hide file tree
Showing 2 changed files with 47 additions and 0 deletions.
23 changes: 23 additions & 0 deletions packages/react-dom/src/__tests__/ReactDOMRoot-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -420,4 +420,27 @@ describe('ReactDOMRoot', () => {
// Still works in the legacy API
ReactDOM.render(<div />, 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, <App />)).toErrorDev(
'You passed a JSX element to createRoot. You probably meant to call ' +
'root.render instead',
{
withoutStack: true,
},
);
});
});
24 changes: 24 additions & 0 deletions packages/react-dom/src/client/ReactDOMRoot.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -174,6 +175,20 @@ export function createRoot(
console.warn(
'hydrate through createRoot is deprecated. Use ReactDOM.hydrateRoot(container, <App />) 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(<App />);',
);
}
}
}
if (options.unstable_strictMode === true) {
Expand Down Expand Up @@ -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, <App />)',
);
}
}

// For now we reuse the whole bag of options since they contain
// the hydration callbacks.
const hydrationCallbacks = options != null ? options : null;
Expand Down

0 comments on commit 8c4cd65

Please sign in to comment.