Skip to content

Commit 8c4cd65

Browse files
authored
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.
1 parent a5b2215 commit 8c4cd65

File tree

2 files changed

+47
-0
lines changed

2 files changed

+47
-0
lines changed

packages/react-dom/src/__tests__/ReactDOMRoot-test.js

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -420,4 +420,27 @@ describe('ReactDOMRoot', () => {
420420
// Still works in the legacy API
421421
ReactDOM.render(<div />, commentNode);
422422
});
423+
424+
it('warn if no children passed to hydrateRoot', async () => {
425+
expect(() =>
426+
ReactDOM.hydrateRoot(container),
427+
).toErrorDev(
428+
'Must provide initial children as second argument to hydrateRoot.',
429+
{withoutStack: true},
430+
);
431+
});
432+
433+
it('warn if JSX passed to createRoot', async () => {
434+
function App() {
435+
return 'Child';
436+
}
437+
438+
expect(() => ReactDOM.createRoot(container, <App />)).toErrorDev(
439+
'You passed a JSX element to createRoot. You probably meant to call ' +
440+
'root.render instead',
441+
{
442+
withoutStack: true,
443+
},
444+
);
445+
});
423446
});

packages/react-dom/src/client/ReactDOMRoot.js

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import type {
1515
} from 'react-reconciler/src/ReactInternalTypes';
1616

1717
import {queueExplicitHydrationTarget} from '../events/ReactDOMEventReplaying';
18+
import {REACT_ELEMENT_TYPE} from 'shared/ReactSymbols';
1819

1920
export type RootType = {
2021
render(children: ReactNodeList): void,
@@ -174,6 +175,20 @@ export function createRoot(
174175
console.warn(
175176
'hydrate through createRoot is deprecated. Use ReactDOM.hydrateRoot(container, <App />) instead.',
176177
);
178+
} else {
179+
if (
180+
typeof options === 'object' &&
181+
options !== null &&
182+
(options: any).$$typeof === REACT_ELEMENT_TYPE
183+
) {
184+
console.error(
185+
'You passed a JSX element to createRoot. You probably meant to ' +
186+
'call root.render instead. ' +
187+
'Example usage:\n\n' +
188+
' let root = createRoot(domContainer);\n' +
189+
' root.render(<App />);',
190+
);
191+
}
177192
}
178193
}
179194
if (options.unstable_strictMode === true) {
@@ -237,6 +252,15 @@ export function hydrateRoot(
237252

238253
warnIfReactDOMContainerInDEV(container);
239254

255+
if (__DEV__) {
256+
if (initialChildren === undefined) {
257+
console.error(
258+
'Must provide initial children as second argument to hydrateRoot. ' +
259+
'Example usage: hydrateRoot(domContainer, <App />)',
260+
);
261+
}
262+
}
263+
240264
// For now we reuse the whole bag of options since they contain
241265
// the hydration callbacks.
242266
const hydrationCallbacks = options != null ? options : null;

0 commit comments

Comments
 (0)