main
js 243 lines 7.21 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 {ReactPortal, ReactNodeList} from 'shared/ReactTypes';
11 import type {ElementRef, Element, ElementType} from 'react';
12 import type {FiberRoot} from 'react-reconciler/src/ReactInternalTypes';
13 import type {RenderRootOptions} from './ReactNativeTypes';
14
15 import './ReactFabricInjection';
16
17 import {
18 batchedUpdates as batchedUpdatesImpl,
19 discreteUpdates,
20 createContainer,
21 updateContainer,
22 injectIntoDevTools,
23 getPublicRootInstance,
24 defaultOnUncaughtError,
25 defaultOnCaughtError,
26 defaultOnRecoverableError,
27 } from 'react-reconciler/src/ReactFiberReconciler';
28
29 import {createPortal as createPortalImpl} from 'react-reconciler/src/ReactPortal';
30 import {setBatchingImplementation} from './legacy-events/ReactGenericBatching';
31
32 import {LegacyRoot, ConcurrentRoot} from 'react-reconciler/src/ReactRootTags';
33 import {
34 findHostInstance_DEPRECATED,
35 findNodeHandle,
36 dispatchCommand,
37 sendAccessibilityEvent,
38 getNodeFromInternalInstanceHandle,
39 isChildPublicInstance,
40 } from './ReactNativePublicCompat';
41 import {getPublicInstanceFromInternalInstanceHandle} from './ReactFiberConfigFabric';
42
43 // Module provided by RN:
44 import {
45 ReactFiberErrorDialog,
46 createPublicRootInstance,
47 type PublicRootInstance,
48 } from 'react-native/react-private-interface';
49 import {
50 disableLegacyMode,
51 enableDefaultTransitionIndicator,
52 } from 'shared/ReactFeatureFlags';
53
54 if (typeof ReactFiberErrorDialog.showErrorDialog !== 'function') {
55 throw new Error(
56 'Expected ReactFiberErrorDialog.showErrorDialog to be a function.',
57 );
58 }
59
60 function nativeOnUncaughtError(
61 error: mixed,
62 errorInfo: {+componentStack?: ?string},
63 ): void {
64 const componentStack =
65 errorInfo.componentStack != null ? errorInfo.componentStack : '';
66 const logError = ReactFiberErrorDialog.showErrorDialog({
67 errorBoundary: null,
68 error,
69 componentStack,
70 });
71
72 // Allow injected showErrorDialog() to prevent default console.error logging.
73 // This enables renderers like ReactNative to better manage redbox behavior.
74 if (logError === false) {
75 return;
76 }
77
78 defaultOnUncaughtError(error, errorInfo);
79 }
80 function nativeOnCaughtError(
81 error: mixed,
82 errorInfo: {
83 +componentStack?: ?string,
84 +errorBoundary?: ?component(...props: any),
85 },
86 ): void {
87 const errorBoundary = errorInfo.errorBoundary;
88 const componentStack =
89 errorInfo.componentStack != null ? errorInfo.componentStack : '';
90 const logError = ReactFiberErrorDialog.showErrorDialog({
91 errorBoundary,
92 error,
93 componentStack,
94 });
95
96 // Allow injected showErrorDialog() to prevent default console.error logging.
97 // This enables renderers like ReactNative to better manage redbox behavior.
98 if (logError === false) {
99 return;
100 }
101
102 defaultOnCaughtError(error, errorInfo);
103 }
104 function nativeOnDefaultTransitionIndicator(): void | (() => void) {
105 // Native doesn't have a default indicator.
106 }
107
108 function render(
109 element: Element<ElementType>,
110 containerTag: number,
111 callback: ?() => void,
112 concurrentRoot: ?boolean,
113 options: ?RenderRootOptions,
114 ): ?ElementRef<ElementType> {
115 if (disableLegacyMode && !concurrentRoot) {
116 throw new Error('render: Unsupported Legacy Mode API.');
117 }
118
119 let root = roots.get(containerTag);
120
121 if (!root) {
122 // TODO: these defaults are for backwards compatibility.
123 // Once RN implements these options internally,
124 // we can remove the defaults and ReactFiberErrorDialog.
125 let onUncaughtError = nativeOnUncaughtError;
126 let onCaughtError = nativeOnCaughtError;
127 let onRecoverableError = defaultOnRecoverableError;
128
129 if (options && options.onUncaughtError !== undefined) {
130 onUncaughtError = options.onUncaughtError;
131 }
132 if (options && options.onCaughtError !== undefined) {
133 onCaughtError = options.onCaughtError;
134 }
135 if (options && options.onRecoverableError !== undefined) {
136 onRecoverableError = options.onRecoverableError;
137 }
138 let onDefaultTransitionIndicator = nativeOnDefaultTransitionIndicator;
139 if (enableDefaultTransitionIndicator) {
140 if (options && options.onDefaultTransitionIndicator !== undefined) {
141 onDefaultTransitionIndicator = options.onDefaultTransitionIndicator;
142 }
143 }
144
145 const publicRootInstance = createPublicRootInstance(containerTag);
146 const rootInstance = {
147 publicInstance: publicRootInstance,
148 containerTag,
149 };
150
151 // TODO (bvaughn): If we decide to keep the wrapper component,
152 // We could create a wrapper for containerTag as well to reduce special casing.
153 root = createContainer(
154 // $FlowFixMe[incompatible-type]
155 rootInstance,
156 concurrentRoot ? ConcurrentRoot : LegacyRoot,
157 null,
158 false,
159 null,
160 '',
161 onUncaughtError,
162 onCaughtError,
163 onRecoverableError,
164 onDefaultTransitionIndicator,
165 null,
166 );
167
168 roots.set(containerTag, root);
169 }
170 // $FlowFixMe[incompatible-type]
171 updateContainer(element, root, null, callback);
172
173 return getPublicRootInstance(root);
174 }
175
176 // $FlowFixMe[missing-this-annot]
177 function unmountComponentAtNode(containerTag: number) {
178 this.stopSurface(containerTag);
179 }
180
181 function stopSurface(containerTag: number) {
182 const root = roots.get(containerTag);
183 if (root) {
184 // TODO: Is it safe to reset this now or should I wait since this unmount could be deferred?
185 updateContainer(null, root, null, () => {
186 // Remove the reference to the public instance to prevent memory leaks.
187 root.containerInfo.publicInstance = null;
188
189 roots.delete(containerTag);
190 });
191 }
192 }
193
194 function createPortal(
195 children: ReactNodeList,
196 containerTag: number,
197 key: ?string = null,
198 ): ReactPortal {
199 return createPortalImpl(children, containerTag, null, key);
200 }
201
202 function getPublicInstanceFromRootTag(
203 rootTag: number,
204 ): PublicRootInstance | null {
205 const root = roots.get(rootTag);
206 if (root) {
207 return root.containerInfo.publicInstance;
208 }
209 return null;
210 }
211
212 setBatchingImplementation(batchedUpdatesImpl, discreteUpdates);
213
214 const roots = new Map<number, FiberRoot>();
215
216 export {
217 // This is needed for implementation details of TouchableNativeFeedback
218 // Remove this once TouchableNativeFeedback doesn't use cloneElement
219 findHostInstance_DEPRECATED,
220 findNodeHandle,
221 dispatchCommand,
222 sendAccessibilityEvent,
223 render,
224 // Deprecated - this function is being renamed to stopSurface, use that instead.
225 // TODO (T47576999): Delete this once it's no longer called from native code.
226 unmountComponentAtNode,
227 stopSurface,
228 createPortal,
229 // The public instance has a reference to the internal instance handle.
230 // This method allows it to acess the most recent shadow node for
231 // the instance (it's only accessible through it).
232 getNodeFromInternalInstanceHandle,
233 // Fabric native methods to traverse the host tree return the same internal
234 // instance handles we use to dispatch events. This provides a way to access
235 // the public instances we created from them (potentially created lazily).
236 getPublicInstanceFromInternalInstanceHandle,
237 // Returns the document instance for that root tag.
238 getPublicInstanceFromRootTag,
239 // DEV-only:
240 isChildPublicInstance,
241 };
242
243 injectIntoDevTools();