main
js 380 lines 12.1 KB
Raw
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, ReactFormState} from 'shared/ReactTypes';
11 import type {
12 FiberRoot,
13 TransitionTracingCallbacks,
14 } from 'react-reconciler/src/ReactInternalTypes';
15
16 import {isValidContainer} from 'react-dom-bindings/src/client/ReactDOMContainer';
17 import {queueExplicitHydrationTarget} from 'react-dom-bindings/src/events/ReactDOMEventReplaying';
18 import {REACT_ELEMENT_TYPE} from 'shared/ReactSymbols';
19 import {
20 disableCommentsAsDOMContainers,
21 enableDefaultTransitionIndicator,
22 } from 'shared/ReactFeatureFlags';
23
24 export type RootType = {
25 render(children: ReactNodeList): void,
26 unmount(): void,
27 _internalRoot: FiberRoot | null,
28 };
29
30 export type CreateRootOptions = {
31 unstable_strictMode?: boolean,
32 unstable_transitionCallbacks?: TransitionTracingCallbacks,
33 identifierPrefix?: string,
34 onUncaughtError?: (
35 error: mixed,
36 errorInfo: {+componentStack?: ?string},
37 ) => void,
38 onCaughtError?: (
39 error: mixed,
40 errorInfo: {
41 +componentStack?: ?string,
42 +errorBoundary?: ?component(...props: any),
43 },
44 ) => void,
45 onRecoverableError?: (
46 error: mixed,
47 errorInfo: {+componentStack?: ?string},
48 ) => void,
49 onDefaultTransitionIndicator?: () => void | (() => void),
50 };
51
52 export type HydrateRootOptions = {
53 // Hydration options
54 onHydrated?: (hydrationBoundary: Comment) => void,
55 onDeleted?: (hydrationBoundary: Comment) => void,
56 // Options for all roots
57 unstable_strictMode?: boolean,
58 unstable_transitionCallbacks?: TransitionTracingCallbacks,
59 identifierPrefix?: string,
60 onUncaughtError?: (
61 error: mixed,
62 errorInfo: {+componentStack?: ?string},
63 ) => void,
64 onCaughtError?: (
65 error: mixed,
66 errorInfo: {
67 +componentStack?: ?string,
68 +errorBoundary?: ?component(...props: any),
69 },
70 ) => void,
71 onRecoverableError?: (
72 error: mixed,
73 errorInfo: {+componentStack?: ?string},
74 ) => void,
75 onDefaultTransitionIndicator?: () => void | (() => void),
76 formState?: ReactFormState<any, any> | null,
77 };
78
79 import {
80 isContainerMarkedAsRoot,
81 markContainerAsRoot,
82 unmarkContainerAsRoot,
83 } from 'react-dom-bindings/src/client/ReactDOMComponentTree';
84 import {listenToAllSupportedEvents} from 'react-dom-bindings/src/events/DOMPluginEventSystem';
85 import {COMMENT_NODE} from 'react-dom-bindings/src/client/HTMLNodeType';
86
87 import {
88 createContainer,
89 createHydrationContainer,
90 updateContainer,
91 updateContainerSync,
92 flushSyncWork,
93 isAlreadyRendering,
94 defaultOnUncaughtError,
95 defaultOnCaughtError,
96 defaultOnRecoverableError,
97 } from 'react-reconciler/src/ReactFiberReconciler';
98 import {defaultOnDefaultTransitionIndicator} from './ReactDOMDefaultTransitionIndicator';
99 import {ConcurrentRoot} from 'react-reconciler/src/ReactRootTags';
100
101 // $FlowFixMe[missing-this-annot]
102 function ReactDOMRoot(internalRoot: FiberRoot) {
103 this._internalRoot = internalRoot;
104 }
105
106 // $FlowFixMe[prop-missing] found when upgrading Flow
107 ReactDOMHydrationRoot.prototype.render = ReactDOMRoot.prototype.render =
108 // $FlowFixMe[missing-this-annot]
109 function (children: ReactNodeList): void {
110 const root = this._internalRoot;
111 if (root === null) {
112 throw new Error('Cannot update an unmounted root.');
113 }
114
115 if (__DEV__) {
116 // using a reference to `arguments` bails out of GCC optimizations which affect function arity
117 const args = arguments;
118 if (typeof args[1] === 'function') {
119 console.error(
120 'does not support the second callback argument. ' +
121 'To execute a side effect after rendering, declare it in a component body with useEffect().',
122 );
123 } else if (isValidContainer(args[1])) {
124 console.error(
125 'You passed a container to the second argument of root.render(...). ' +
126 "You don't need to pass it again since you already passed it to create the root.",
127 );
128 } else if (typeof args[1] !== 'undefined') {
129 console.error(
130 'You passed a second argument to root.render(...) but it only accepts ' +
131 'one argument.',
132 );
133 }
134 }
135 updateContainer(children, root, null, null);
136 };
137
138 // $FlowFixMe[prop-missing] found when upgrading Flow
139 ReactDOMHydrationRoot.prototype.unmount = ReactDOMRoot.prototype.unmount =
140 // $FlowFixMe[missing-this-annot]
141 function (): void {
142 if (__DEV__) {
143 // using a reference to `arguments` bails out of GCC optimizations which affect function arity
144 const args = arguments;
145 if (typeof args[0] === 'function') {
146 console.error(
147 'does not support a callback argument. ' +
148 'To execute a side effect after rendering, declare it in a component body with useEffect().',
149 );
150 }
151 }
152 const root = this._internalRoot;
153 if (root !== null) {
154 this._internalRoot = null;
155 const container = root.containerInfo;
156 if (__DEV__) {
157 if (isAlreadyRendering()) {
158 console.error(
159 'Attempted to synchronously unmount a root while React was already ' +
160 'rendering. React cannot finish unmounting the root until the ' +
161 'current render has completed, which may lead to a race condition.',
162 );
163 }
164 }
165 updateContainerSync(null, root, null, null);
166 flushSyncWork();
167 unmarkContainerAsRoot(container);
168 }
169 };
170
171 export function createRoot(
172 container: Element | Document | DocumentFragment,
173 options?: CreateRootOptions,
174 ): RootType {
175 if (!isValidContainer(container)) {
176 throw new Error('Target container is not a DOM element.');
177 }
178
179 warnIfReactDOMContainerInDEV(container);
180
181 const concurrentUpdatesByDefaultOverride = false;
182 let isStrictMode = false;
183 let identifierPrefix = '';
184 let onUncaughtError = defaultOnUncaughtError;
185 let onCaughtError = defaultOnCaughtError;
186 let onRecoverableError = defaultOnRecoverableError;
187 let onDefaultTransitionIndicator = defaultOnDefaultTransitionIndicator;
188 let transitionCallbacks = null;
189
190 // $FlowFixMe[invalid-compare]
191 if (options !== null && options !== undefined) {
192 if (__DEV__) {
193 if ((options as any).hydrate) {
194 console.warn(
195 'hydrate through createRoot is deprecated. Use ReactDOMClient.hydrateRoot(container, <App />) instead.',
196 );
197 } else {
198 if (
199 typeof options === 'object' &&
200 // $FlowFixMe[invalid-compare]
201 options !== null &&
202 (options as any).$$typeof === REACT_ELEMENT_TYPE
203 ) {
204 console.error(
205 'You passed a JSX element to createRoot. You probably meant to ' +
206 'call root.render instead. ' +
207 'Example usage:\n\n' +
208 ' let root = createRoot(domContainer);\n' +
209 ' root.render(<App />);',
210 );
211 }
212 }
213 }
214 if (options.unstable_strictMode === true) {
215 isStrictMode = true;
216 }
217 if (options.identifierPrefix !== undefined) {
218 identifierPrefix = options.identifierPrefix;
219 }
220 if (options.onUncaughtError !== undefined) {
221 onUncaughtError = options.onUncaughtError;
222 }
223 if (options.onCaughtError !== undefined) {
224 onCaughtError = options.onCaughtError;
225 }
226 if (options.onRecoverableError !== undefined) {
227 onRecoverableError = options.onRecoverableError;
228 }
229 if (enableDefaultTransitionIndicator) {
230 if (options.onDefaultTransitionIndicator !== undefined) {
231 onDefaultTransitionIndicator = options.onDefaultTransitionIndicator;
232 }
233 }
234 if (options.unstable_transitionCallbacks !== undefined) {
235 transitionCallbacks = options.unstable_transitionCallbacks;
236 }
237 }
238
239 const root = createContainer(
240 container,
241 ConcurrentRoot,
242 null,
243 isStrictMode,
244 concurrentUpdatesByDefaultOverride,
245 identifierPrefix,
246 onUncaughtError,
247 onCaughtError,
248 onRecoverableError,
249 onDefaultTransitionIndicator,
250 transitionCallbacks,
251 );
252 markContainerAsRoot(root.current, container);
253
254 const rootContainerElement: Document | Element | DocumentFragment =
255 !disableCommentsAsDOMContainers && container.nodeType === COMMENT_NODE
256 ? (container.parentNode as any)
257 : container;
258 listenToAllSupportedEvents(rootContainerElement);
259
260 // $FlowFixMe[invalid-constructor] Flow no longer supports calling new on functions
261 return new ReactDOMRoot(root);
262 }
263
264 // $FlowFixMe[missing-this-annot]
265 function ReactDOMHydrationRoot(internalRoot: FiberRoot) {
266 this._internalRoot = internalRoot;
267 }
268 function scheduleHydration(target: Node) {
269 if (target) {
270 queueExplicitHydrationTarget(target);
271 }
272 }
273 // $FlowFixMe[prop-missing] found when upgrading Flow
274 ReactDOMHydrationRoot.prototype.unstable_scheduleHydration = scheduleHydration;
275
276 export function hydrateRoot(
277 container: Document | Element,
278 initialChildren: ReactNodeList,
279 options?: HydrateRootOptions,
280 ): RootType {
281 if (!isValidContainer(container)) {
282 throw new Error('Target container is not a DOM element.');
283 }
284
285 warnIfReactDOMContainerInDEV(container);
286
287 if (__DEV__) {
288 if (initialChildren === undefined) {
289 console.error(
290 'Must provide initial children as second argument to hydrateRoot. ' +
291 'Example usage: hydrateRoot(domContainer, <App />)',
292 );
293 }
294 }
295
296 // For now we reuse the whole bag of options since they contain
297 // the hydration callbacks.
298 const hydrationCallbacks = options != null ? options : null;
299
300 const concurrentUpdatesByDefaultOverride = false;
301 let isStrictMode = false;
302 let identifierPrefix = '';
303 let onUncaughtError = defaultOnUncaughtError;
304 let onCaughtError = defaultOnCaughtError;
305 let onRecoverableError = defaultOnRecoverableError;
306 let onDefaultTransitionIndicator = defaultOnDefaultTransitionIndicator;
307 let transitionCallbacks = null;
308 let formState = null;
309 // $FlowFixMe[invalid-compare]
310 if (options !== null && options !== undefined) {
311 if (options.unstable_strictMode === true) {
312 isStrictMode = true;
313 }
314 if (options.identifierPrefix !== undefined) {
315 identifierPrefix = options.identifierPrefix;
316 }
317 if (options.onUncaughtError !== undefined) {
318 onUncaughtError = options.onUncaughtError;
319 }
320 if (options.onCaughtError !== undefined) {
321 onCaughtError = options.onCaughtError;
322 }
323 if (options.onRecoverableError !== undefined) {
324 onRecoverableError = options.onRecoverableError;
325 }
326 if (enableDefaultTransitionIndicator) {
327 if (options.onDefaultTransitionIndicator !== undefined) {
328 onDefaultTransitionIndicator = options.onDefaultTransitionIndicator;
329 }
330 }
331 if (options.unstable_transitionCallbacks !== undefined) {
332 transitionCallbacks = options.unstable_transitionCallbacks;
333 }
334 if (options.formState !== undefined) {
335 formState = options.formState;
336 }
337 }
338
339 const root = createHydrationContainer(
340 initialChildren,
341 null,
342 container,
343 ConcurrentRoot,
344 hydrationCallbacks,
345 isStrictMode,
346 concurrentUpdatesByDefaultOverride,
347 identifierPrefix,
348 onUncaughtError,
349 onCaughtError,
350 onRecoverableError,
351 onDefaultTransitionIndicator,
352 transitionCallbacks,
353 formState,
354 );
355 markContainerAsRoot(root.current, container);
356 // This can't be a comment node since hydration doesn't work on comment nodes anyway.
357 listenToAllSupportedEvents(container);
358
359 // $FlowFixMe[invalid-constructor] Flow no longer supports calling new on functions
360 return new ReactDOMHydrationRoot(root);
361 }
362
363 function warnIfReactDOMContainerInDEV(container: any) {
364 if (__DEV__) {
365 if (isContainerMarkedAsRoot(container)) {
366 if (container._reactRootContainer) {
367 console.error(
368 'You are calling ReactDOMClient.createRoot() on a container that was previously ' +
369 'passed to ReactDOM.render(). This is not supported.',
370 );
371 } else {
372 console.error(
373 'You are calling ReactDOMClient.createRoot() on a container that ' +
374 'has already been passed to createRoot() before. Instead, call ' +
375 'root.render() on the existing root instead if you want to update it.',
376 );
377 }
378 }
379 }
380 }