1
+/**
2
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ * @flow
8
+ */
9
+
10
+import type {ReactNodeList} from 'shared/ReactTypes';
11
+
12
+import type {
13
+ RootType,
14
+ CreateRootOptions,
15
+ HydrateRootOptions,
16
+} from './ReactDOMRoot';
17
+
18
+import type {FiberRoot} from 'react-reconciler/src/ReactInternalTypes';
19
+
20
+import type {
21
+ Container,
22
+ PublicInstance,
23
+} from 'react-dom-bindings/src/client/ReactFiberConfigDOM';
24
+
25
+import {
26
+ createRoot as createRootImpl,
27
+ hydrateRoot as hydrateRootImpl,
28
+} from './ReactDOMRoot';
29
+
30
+import {disableLegacyMode} from 'shared/ReactFeatureFlags';
31
+import {clearContainer} from 'react-dom-bindings/src/client/ReactFiberConfigDOM';
32
+import {
33
+ getInstanceFromNode,
34
+ isContainerMarkedAsRoot,
35
+ markContainerAsRoot,
36
+} from 'react-dom-bindings/src/client/ReactDOMComponentTree';
37
+import {listenToAllSupportedEvents} from 'react-dom-bindings/src/events/DOMPluginEventSystem';
38
+import {isValidContainerLegacy} from './ReactDOMRoot';
39
+import {
40
+ DOCUMENT_NODE,
41
+ COMMENT_NODE,
42
+} from 'react-dom-bindings/src/client/HTMLNodeType';
43
+
44
+import {
45
+ createContainer,
46
+ createHydrationContainer,
47
+ findHostInstanceWithNoPortals,
48
+ updateContainer,
49
+ flushSync,
50
+ getPublicRootInstance,
51
+ defaultOnUncaughtError,
52
+ defaultOnCaughtError,
53
+} from 'react-reconciler/src/ReactFiberReconciler';
54
+import {LegacyRoot} from 'react-reconciler/src/ReactRootTags';
55
+import {has as hasInstance} from 'shared/ReactInstanceMap';
56
+
57
+import assign from 'shared/assign';
58
+
59
+// Provided by www
60
+const ReactFiberErrorDialogWWW = require('ReactFiberErrorDialog');
61
+
62
+if (typeof ReactFiberErrorDialogWWW.showErrorDialog !== 'function') {
63
+ throw new Error(
64
+ 'Expected ReactFiberErrorDialog.showErrorDialog to be a function.',
65
+ );
66
+}
67
+
68
+function wwwOnUncaughtError(
69
+ error: mixed,
70
+ errorInfo: {+componentStack?: ?string},
71
+): void {
72
+ const componentStack =
73
+ errorInfo.componentStack != null ? errorInfo.componentStack : '';
74
+ const logError = ReactFiberErrorDialogWWW.showErrorDialog({
75
+ errorBoundary: null,
76
+ error,
77
+ componentStack,
78
+ });
79
+
80
+ // Allow injected showErrorDialog() to prevent default console.error logging.
81
+ // This enables renderers like ReactNative to better manage redbox behavior.
82
+ if (logError === false) {
83
+ return;
84
+ }
85
+
86
+ defaultOnUncaughtError(error, errorInfo);
87
+}
88
+
89
+function wwwOnCaughtError(
90
+ error: mixed,
91
+ errorInfo: {
92
+ +componentStack?: ?string,
93
+ +errorBoundary?: ?React$Component<any, any>,
94
+ },
95
+): void {
96
+ const errorBoundary = errorInfo.errorBoundary;
97
+ const componentStack =
98
+ errorInfo.componentStack != null ? errorInfo.componentStack : '';
99
+ const logError = ReactFiberErrorDialogWWW.showErrorDialog({
100
+ errorBoundary,
101
+ error,
102
+ componentStack,
103
+ });
104
+
105
+ // Allow injected showErrorDialog() to prevent default console.error logging.
106
+ // This enables renderers like ReactNative to better manage redbox behavior.
107
+ if (logError === false) {
108
+ return;
109
+ }
110
+
111
+ defaultOnCaughtError(error, errorInfo);
112
+}
113
+
114
+export function createRoot(
115
+ container: Element | Document | DocumentFragment,
116
+ options?: CreateRootOptions,
117
+): RootType {
118
+ return createRootImpl(
119
+ container,
120
+ assign(
121
+ ({
122
+ onUncaughtError: wwwOnUncaughtError,
123
+ onCaughtError: wwwOnCaughtError,
124
+ }: any),
125
+ options,
126
+ ),
127
+ );
128
+}
129
+
130
+export function hydrateRoot(
131
+ container: Document | Element,
132
+ initialChildren: ReactNodeList,
133
+ options?: HydrateRootOptions,
134
+): RootType {
135
+ return hydrateRootImpl(
136
+ container,
137
+ initialChildren,
138
+ assign(
139
+ ({
140
+ onUncaughtError: wwwOnUncaughtError,
141
+ onCaughtError: wwwOnCaughtError,
142
+ }: any),
143
+ options,
144
+ ),
145
+ );
146
+}
147
+
148
+let topLevelUpdateWarnings;
149
+
150
+if (__DEV__) {
151
+ topLevelUpdateWarnings = (container: Container) => {
152
+ if (container._reactRootContainer && container.nodeType !== COMMENT_NODE) {
153
+ const hostInstance = findHostInstanceWithNoPortals(
154
+ container._reactRootContainer.current,
155
+ );
156
+ if (hostInstance) {
157
+ if (hostInstance.parentNode !== container) {
158
+ console.error(
159
+ 'It looks like the React-rendered content of this ' +
160
+ 'container was removed without using React. This is not ' +
161
+ 'supported and will cause errors. Instead, call ' +
162
+ 'ReactDOM.unmountComponentAtNode to empty a container.',
163
+ );
164
+ }
165
+ }
166
+ }
167
+
168
+ const isRootRenderedBySomeReact = !!container._reactRootContainer;
169
+ const rootEl = getReactRootElementInContainer(container);
170
+ const hasNonRootReactChild = !!(rootEl && getInstanceFromNode(rootEl));
171
+
172
+ if (hasNonRootReactChild && !isRootRenderedBySomeReact) {
173
+ console.error(
174
+ 'Replacing React-rendered children with a new root ' +
175
+ 'component. If you intended to update the children of this node, ' +
176
+ 'you should instead have the existing children update their state ' +
177
+ 'and render the new components instead of calling ReactDOM.render.',
178
+ );
179
+ }
180
+ };
181
+}
182
+
183
+function getReactRootElementInContainer(container: any) {
184
+ if (!container) {
185
+ return null;
186
+ }
187
+
188
+ if (container.nodeType === DOCUMENT_NODE) {
189
+ return container.documentElement;
190
+ } else {
191
+ return container.firstChild;
192
+ }
193
+}
194
+
195
+function noopOnRecoverableError() {
196
+ // This isn't reachable because onRecoverableError isn't called in the
197
+ // legacy API.
198
+}
199
+
200
+function legacyCreateRootFromDOMContainer(
201
+ container: Container,
202
+ initialChildren: ReactNodeList,
203
+ parentComponent: ?React$Component<any, any>,
204
+ callback: ?Function,
205
+ isHydrationContainer: boolean,
206
+): FiberRoot {
207
+ if (isHydrationContainer) {
208
+ if (typeof callback === 'function') {
209
+ const originalCallback = callback;
210
+ callback = function () {
211
+ const instance = getPublicRootInstance(root);
212
+ originalCallback.call(instance);
213
+ };
214
+ }
215
+
216
+ const root: FiberRoot = createHydrationContainer(
217
+ initialChildren,
218
+ callback,
219
+ container,
220
+ LegacyRoot,
221
+ null, // hydrationCallbacks
222
+ false, // isStrictMode
223
+ false, // concurrentUpdatesByDefaultOverride,
224
+ '', // identifierPrefix
225
+ wwwOnUncaughtError,
226
+ wwwOnCaughtError,
227
+ noopOnRecoverableError,
228
+ // TODO(luna) Support hydration later
229
+ null,
230
+ null,
231
+ );
232
+ container._reactRootContainer = root;
233
+ markContainerAsRoot(root.current, container);
234
+
235
+ const rootContainerElement =
236
+ container.nodeType === COMMENT_NODE ? container.parentNode : container;
237
+ // $FlowFixMe[incompatible-call]
238
+ listenToAllSupportedEvents(rootContainerElement);
239
+
240
+ flushSync();
241
+ return root;
242
+ } else {
243
+ // First clear any existing content.
244
+ clearContainer(container);
245
+
246
+ if (typeof callback === 'function') {
247
+ const originalCallback = callback;
248
+ callback = function () {
249
+ const instance = getPublicRootInstance(root);
250
+ originalCallback.call(instance);
251
+ };
252
+ }
253
+
254
+ const root = createContainer(
255
+ container,
256
+ LegacyRoot,
257
+ null, // hydrationCallbacks
258
+ false, // isStrictMode
259
+ false, // concurrentUpdatesByDefaultOverride,
260
+ '', // identifierPrefix
261
+ wwwOnUncaughtError,
262
+ wwwOnCaughtError,
263
+ noopOnRecoverableError,
264
+ null, // transitionCallbacks
265
+ );
266
+ container._reactRootContainer = root;
267
+ markContainerAsRoot(root.current, container);
268
+
269
+ const rootContainerElement =
270
+ container.nodeType === COMMENT_NODE ? container.parentNode : container;
271
+ // $FlowFixMe[incompatible-call]
272
+ listenToAllSupportedEvents(rootContainerElement);
273
+
274
+ // Initial mount should not be batched.
275
+ flushSync(() => {
276
+ updateContainer(initialChildren, root, parentComponent, callback);
277
+ });
278
+
279
+ return root;
280
+ }
281
+}
282
+
283
+function warnOnInvalidCallback(callback: mixed): void {
284
+ if (__DEV__) {
285
+ if (callback !== null && typeof callback !== 'function') {
286
+ console.error(
287
+ 'Expected the last optional `callback` argument to be a ' +
288
+ 'function. Instead received: %s.',
289
+ callback,
290
+ );
291
+ }
292
+ }
293
+}
294
+
295
+function legacyRenderSubtreeIntoContainer(
296
+ parentComponent: ?React$Component<any, any>,
297
+ children: ReactNodeList,
298
+ container: Container,
299
+ forceHydrate: boolean,
300
+ callback: ?Function,
301
+): React$Component<any, any> | PublicInstance | null {
302
+ if (__DEV__) {
303
+ topLevelUpdateWarnings(container);
304
+ warnOnInvalidCallback(callback === undefined ? null : callback);
305
+ }
306
+
307
+ const maybeRoot = container._reactRootContainer;
308
+ let root: FiberRoot;
309
+ if (!maybeRoot) {
310
+ // Initial mount
311
+ root = legacyCreateRootFromDOMContainer(
312
+ container,
313
+ children,
314
+ parentComponent,
315
+ callback,
316
+ forceHydrate,
317
+ );
318
+ } else {
319
+ root = maybeRoot;
320
+ if (typeof callback === 'function') {
321
+ const originalCallback = callback;
322
+ callback = function () {
323
+ const instance = getPublicRootInstance(root);
324
+ originalCallback.call(instance);
325
+ };
326
+ }
327
+ // Update
328
+ updateContainer(children, root, parentComponent, callback);
329
+ }
330
+ return getPublicRootInstance(root);
331
+}
332
+
333
+export function render(
334
+ element: React$Element<any>,
335
+ container: Container,
336
+ callback: ?Function,
337
+): React$Component<any, any> | PublicInstance | null {
338
+ if (disableLegacyMode) {
339
+ if (__DEV__) {
340
+ console.error(
341
+ 'ReactDOM.render was removed in React 19. Use createRoot instead.',
342
+ );
343
+ }
344
+ throw new Error('ReactDOM: Unsupported Legacy Mode API.');
345
+ }
346
+ if (__DEV__) {
347
+ console.error(
348
+ 'ReactDOM.render has not been supported since React 18. Use createRoot ' +
349
+ 'instead. Until you switch to the new API, your app will behave as ' +
350
+ "if it's running React 17. Learn " +
351
+ 'more: https://react.dev/link/switch-to-createroot',
352
+ );
353
+ }
354
+
355
+ if (!isValidContainerLegacy(container)) {
356
+ throw new Error('Target container is not a DOM element.');
357
+ }
358
+
359
+ if (__DEV__) {
360
+ const isModernRoot =
361
+ isContainerMarkedAsRoot(container) &&
362
+ container._reactRootContainer === undefined;
363
+ if (isModernRoot) {
364
+ console.error(
365
+ 'You are calling ReactDOM.render() on a container that was previously ' +
366
+ 'passed to ReactDOMClient.createRoot(). This is not supported. ' +
367
+ 'Did you mean to call root.render(element)?',
368
+ );
369
+ }
370
+ }
371
+ return legacyRenderSubtreeIntoContainer(
372
+ null,
373
+ element,
374
+ container,
375
+ false,
376
+ callback,
377
+ );
378
+}
379
+
380
+export function unstable_renderSubtreeIntoContainer(
381
+ parentComponent: React$Component<any, any>,
382
+ element: React$Element<any>,
383
+ containerNode: Container,
384
+ callback: ?Function,
385
+): React$Component<any, any> | PublicInstance | null {
386
+ if (disableLegacyMode) {
387
+ if (__DEV__) {
388
+ console.error(
389
+ 'ReactDOM.unstable_renderSubtreeIntoContainer() was removed in React 19. Consider using a portal instead.',
390
+ );
391
+ }
392
+ throw new Error('ReactDOM: Unsupported Legacy Mode API.');
393
+ }
394
+ if (__DEV__) {
395
+ console.error(
396
+ 'ReactDOM.unstable_renderSubtreeIntoContainer() has not been supported ' +
397
+ 'since React 18. Consider using a portal instead. Until you switch to ' +
398
+ "the createRoot API, your app will behave as if it's running React " +
399
+ '17. Learn more: https://react.dev/link/switch-to-createroot',
400
+ );
401
+ }
402
+
403
+ if (!isValidContainerLegacy(containerNode)) {
404
+ throw new Error('Target container is not a DOM element.');
405
+ }
406
+
407
+ if (parentComponent == null || !hasInstance(parentComponent)) {
408
+ throw new Error('parentComponent must be a valid React Component');
409
+ }
410
+
411
+ return legacyRenderSubtreeIntoContainer(
412
+ parentComponent,
413
+ element,
414
+ containerNode,
415
+ false,
416
+ callback,
417
+ );
418
+}