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.
Andrew Clark committed
Feb 24, 2022 at 10:57 UTC
8c4cd65cfaa4614bac7fd7783b4ec502a337eba3
2 files changed
+47
packages/react-dom/src/__tests__/ReactDOMRoot-test.js
+23
@@ -420,4 +420,27 @@ describe('ReactDOMRoot', () => {
420
// Still works in the legacy API
421
ReactDOM.render(<div />, commentNode);
422
});
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
+ });
446
});
packages/react-dom/src/client/ReactDOMRoot.js
+24
@@ -15,6 +15,7 @@ import type {
15
} from 'react-reconciler/src/ReactInternalTypes';
16
17
import {queueExplicitHydrationTarget} from '../events/ReactDOMEventReplaying';
18
+import {REACT_ELEMENT_TYPE} from 'shared/ReactSymbols';
19
20
export type RootType = {
21
render(children: ReactNodeList): void,
@@ -174,6 +175,20 @@ export function createRoot(
175
console.warn(
176
'hydrate through createRoot is deprecated. Use ReactDOM.hydrateRoot(container, <App />) instead.',
177
);
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
+ }
192
}
193
}
194
if (options.unstable_strictMode === true) {
@@ -237,6 +252,15 @@ export function hydrateRoot(
252
253
warnIfReactDOMContainerInDEV(container);
254
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
+
264
// For now we reuse the whole bag of options since they contain
265
// the hydration callbacks.
266
const hydrationCallbacks = options != null ? options : null;