@samitouri / QOS-React-2 / commits / 9b79292ae7

Add plumbing for onDefaultTransitionIndicator (#33150)

This just adds the options at the root and wire it up to the root but it doesn't do anything yet.

Sebastian Markbåge committed May 8, 2025 at 20:42 UTC 9b79292ae7c2980acb531f1dff3cf1ca2d2f40b7
18 files changed +92 -4
packages/react-art/src/ReactART.js
+13
@@ -13,7 +13,11 @@ import {
13 updateContainerSync,
14 injectIntoDevTools,
15 flushSyncWork,
16 + defaultOnUncaughtError,
17 + defaultOnCaughtError,
18 + defaultOnRecoverableError,
19 } from 'react-reconciler/src/ReactFiberReconciler';
20 +
21 import Transform from 'art/core/transform';
22 import Mode from 'art/modes/current';
23 import FastNoSideEffects from 'art/modes/fast-noSideEffects';
@@ -21,6 +25,10 @@ import {disableLegacyMode} from 'shared/ReactFeatureFlags';
25
26 import {TYPES, childrenAsString} from './ReactARTInternals';
27
28 +function defaultOnDefaultTransitionIndicator() {
29 + // Noop
30 +}
31 +
32 Mode.setCurrent(
33 // Change to 'art/modes/dom' for easier debugging via SVG
34 FastNoSideEffects,
@@ -75,6 +83,11 @@ class Surface extends React.Component {
83 false,
84 false,
85 '',
86 + defaultOnUncaughtError,
87 + defaultOnCaughtError,
88 + defaultOnRecoverableError,
89 + defaultOnDefaultTransitionIndicator,
90 + null,
91 );
92 // We synchronously flush updates coming from above so that they commit together
93 // and so that refs resolve before the parent life cycles.
packages/react-dom/src/client/ReactDOMRoot.js
+25 -1
@@ -16,7 +16,10 @@ import type {
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 {disableCommentsAsDOMContainers} from 'shared/ReactFeatureFlags';
19 +import {
20 + disableCommentsAsDOMContainers,
21 + enableDefaultTransitionIndicator,
22 +} from 'shared/ReactFeatureFlags';
23
24 export type RootType = {
25 render(children: ReactNodeList): void,
@@ -43,6 +46,7 @@ export type CreateRootOptions = {
46 error: mixed,
47 errorInfo: {+componentStack?: ?string},
48 ) => void,
49 + onDefaultTransitionIndicator?: () => void | (() => void),
50 };
51
52 export type HydrateRootOptions = {
@@ -68,6 +72,7 @@ export type HydrateRootOptions = {
72 error: mixed,
73 errorInfo: {+componentStack?: ?string},
74 ) => void,
75 + onDefaultTransitionIndicator?: () => void | (() => void),
76 formState?: ReactFormState<any, any> | null,
77 };
78
@@ -92,6 +97,11 @@ import {
97 } from 'react-reconciler/src/ReactFiberReconciler';
98 import {ConcurrentRoot} from 'react-reconciler/src/ReactRootTags';
99
100 +function defaultOnDefaultTransitionIndicator(): void | (() => void) {
101 + // TODO: Implement the default
102 + return function () {};
103 +}
104 +
105 // $FlowFixMe[missing-this-annot]
106 function ReactDOMRoot(internalRoot: FiberRoot) {
107 this._internalRoot = internalRoot;
@@ -178,6 +188,7 @@ export function createRoot(
188 let onUncaughtError = defaultOnUncaughtError;
189 let onCaughtError = defaultOnCaughtError;
190 let onRecoverableError = defaultOnRecoverableError;
191 + let onDefaultTransitionIndicator = defaultOnDefaultTransitionIndicator;
192 let transitionCallbacks = null;
193
194 if (options !== null && options !== undefined) {
@@ -217,6 +228,11 @@ export function createRoot(
228 if (options.onRecoverableError !== undefined) {
229 onRecoverableError = options.onRecoverableError;
230 }
231 + if (enableDefaultTransitionIndicator) {
232 + if (options.onDefaultTransitionIndicator !== undefined) {
233 + onDefaultTransitionIndicator = options.onDefaultTransitionIndicator;
234 + }
235 + }
236 if (options.unstable_transitionCallbacks !== undefined) {
237 transitionCallbacks = options.unstable_transitionCallbacks;
238 }
@@ -232,6 +248,7 @@ export function createRoot(
248 onUncaughtError,
249 onCaughtError,
250 onRecoverableError,
251 + onDefaultTransitionIndicator,
252 transitionCallbacks,
253 );
254 markContainerAsRoot(root.current, container);
@@ -288,6 +305,7 @@ export function hydrateRoot(
305 let onUncaughtError = defaultOnUncaughtError;
306 let onCaughtError = defaultOnCaughtError;
307 let onRecoverableError = defaultOnRecoverableError;
308 + let onDefaultTransitionIndicator = defaultOnDefaultTransitionIndicator;
309 let transitionCallbacks = null;
310 let formState = null;
311 if (options !== null && options !== undefined) {
@@ -306,6 +324,11 @@ export function hydrateRoot(
324 if (options.onRecoverableError !== undefined) {
325 onRecoverableError = options.onRecoverableError;
326 }
327 + if (enableDefaultTransitionIndicator) {
328 + if (options.onDefaultTransitionIndicator !== undefined) {
329 + onDefaultTransitionIndicator = options.onDefaultTransitionIndicator;
330 + }
331 + }
332 if (options.unstable_transitionCallbacks !== undefined) {
333 transitionCallbacks = options.unstable_transitionCallbacks;
334 }
@@ -326,6 +349,7 @@ export function hydrateRoot(
349 onUncaughtError,
350 onCaughtError,
351 onRecoverableError,
352 + onDefaultTransitionIndicator,
353 transitionCallbacks,
354 formState,
355 );
packages/react-dom/src/client/ReactDOMRootFB.js
+6
@@ -211,6 +211,10 @@ function noopOnRecoverableError() {
211 // legacy API.
212 }
213
214 +function noopOnDefaultTransitionIndicator() {
215 + // Noop
216 +}
217 +
218 function legacyCreateRootFromDOMContainer(
219 container: Container,
220 initialChildren: ReactNodeList,
@@ -239,6 +243,7 @@ function legacyCreateRootFromDOMContainer(
243 wwwOnUncaughtError,
244 wwwOnCaughtError,
245 noopOnRecoverableError,
246 + noopOnDefaultTransitionIndicator,
247 // TODO(luna) Support hydration later
248 null,
249 null,
@@ -277,6 +282,7 @@ function legacyCreateRootFromDOMContainer(
282 wwwOnUncaughtError,
283 wwwOnCaughtError,
284 noopOnRecoverableError,
285 + noopOnDefaultTransitionIndicator,
286 null, // transitionCallbacks
287 );
288 container._reactRootContainer = root;
packages/react-native-renderer/src/ReactFabric.js
+4
@@ -98,6 +98,9 @@ function nativeOnCaughtError(
98
99 defaultOnCaughtError(error, errorInfo);
100 }
101 +function nativeOnDefaultTransitionIndicator(): void | (() => void) {
102 + // Native doesn't have a default indicator.
103 +}
104
105 function render(
106 element: Element<ElementType>,
@@ -148,6 +151,7 @@ function render(
151 onUncaughtError,
152 onCaughtError,
153 onRecoverableError,
154 + nativeOnDefaultTransitionIndicator,
155 null,
156 );
157
packages/react-native-renderer/src/ReactNativeRenderer.js
+4
@@ -113,6 +113,9 @@ function nativeOnCaughtError(
113
114 defaultOnCaughtError(error, errorInfo);
115 }
116 +function nativeOnDefaultTransitionIndicator(): void | (() => void) {
117 + // Native doesn't have a default indicator.
118 +}
119
120 function render(
121 element: MixedElement,
@@ -162,6 +165,7 @@ function render(
165 onUncaughtError,
166 onCaughtError,
167 onRecoverableError,
168 + nativeOnDefaultTransitionIndicator,
169 null,
170 );
171 roots.set(containerTag, root);
packages/react-noop-renderer/src/createReactNoop.js
+9
@@ -80,6 +80,7 @@ type CreateRootOptions = {
80 unstable_transitionCallbacks?: TransitionTracingCallbacks,
81 onUncaughtError?: (error: mixed, errorInfo: {componentStack: string}) => void,
82 onCaughtError?: (error: mixed, errorInfo: {componentStack: string}) => void,
83 + onDefaultTransitionIndicator?: () => void | (() => void),
84 ...
85 };
86 type InstanceMeasurement = null;
@@ -1141,6 +1142,9 @@ function createReactNoop(reconciler: Function, useMutation: boolean) {
1142 // TODO: Turn this on once tests are fixed
1143 // console.error(error);
1144 }
1145 + function onDefaultTransitionIndicator(): void | (() => void) {
1146 + // TODO: Allow this as an option.
1147 + }
1148
1149 let idCounter = 0;
1150
@@ -1196,6 +1200,7 @@ function createReactNoop(reconciler: Function, useMutation: boolean) {
1200 NoopRenderer.defaultOnUncaughtError,
1201 NoopRenderer.defaultOnCaughtError,
1202 onRecoverableError,
1203 + onDefaultTransitionIndicator,
1204 null,
1205 );
1206 roots.set(rootID, root);
@@ -1224,6 +1229,9 @@ function createReactNoop(reconciler: Function, useMutation: boolean) {
1229 ? options.onCaughtError
1230 : NoopRenderer.defaultOnCaughtError,
1231 onRecoverableError,
1232 + options && options.onDefaultTransitionIndicator
1233 + ? options.onDefaultTransitionIndicator
1234 + : onDefaultTransitionIndicator,
1235 options && options.unstable_transitionCallbacks
1236 ? options.unstable_transitionCallbacks
1237 : null,
@@ -1262,6 +1270,7 @@ function createReactNoop(reconciler: Function, useMutation: boolean) {
1270 NoopRenderer.defaultOnUncaughtError,
1271 NoopRenderer.defaultOnCaughtError,
1272 onRecoverableError,
1273 + onDefaultTransitionIndicator,
1274 null,
1275 );
1276 return {
packages/react-reconciler/src/ReactFiberReconciler.js
+6 -2
@@ -254,6 +254,7 @@ export function createContainer(
254 error: mixed,
255 errorInfo: {+componentStack?: ?string},
256 ) => void,
257 + onDefaultTransitionIndicator: () => void | (() => void),
258 transitionCallbacks: null | TransitionTracingCallbacks,
259 ): OpaqueRoot {
260 const hydrate = false;
@@ -266,11 +267,12 @@ export function createContainer(
267 hydrationCallbacks,
268 isStrictMode,
269 identifierPrefix,
270 + null,
271 onUncaughtError,
272 onCaughtError,
273 onRecoverableError,
274 + onDefaultTransitionIndicator,
275 transitionCallbacks,
273 - null,
276 );
277 }
278
@@ -300,6 +302,7 @@ export function createHydrationContainer(
302 error: mixed,
303 errorInfo: {+componentStack?: ?string},
304 ) => void,
305 + onDefaultTransitionIndicator: () => void | (() => void),
306 transitionCallbacks: null | TransitionTracingCallbacks,
307 formState: ReactFormState<any, any> | null,
308 ): OpaqueRoot {
@@ -312,11 +315,12 @@ export function createHydrationContainer(
315 hydrationCallbacks,
316 isStrictMode,
317 identifierPrefix,
318 + formState,
319 onUncaughtError,
320 onCaughtError,
321 onRecoverableError,
322 + onDefaultTransitionIndicator,
323 transitionCallbacks,
319 - formState,
324 );
325
326 // TODO: Move this to FiberRoot constructor
packages/react-reconciler/src/ReactFiberRoot.js
+9 -1
@@ -35,6 +35,7 @@ import {
35 disableLegacyMode,
36 enableViewTransition,
37 enableGestureTransition,
38 + enableDefaultTransitionIndicator,
39 } from 'shared/ReactFeatureFlags';
40 import {initializeUpdateQueue} from './ReactFiberClassUpdateQueue';
41 import {LegacyRoot, ConcurrentRoot} from './ReactRootTags';
@@ -56,6 +57,7 @@ function FiberRootNode(
57 onUncaughtError: any,
58 onCaughtError: any,
59 onRecoverableError: any,
60 + onDefaultTransitionIndicator: any,
61 formState: ReactFormState<any, any> | null,
62 ) {
63 this.tag = disableLegacyMode ? ConcurrentRoot : tag;
@@ -90,6 +92,10 @@ function FiberRootNode(
92 this.onCaughtError = onCaughtError;
93 this.onRecoverableError = onRecoverableError;
94
95 + if (enableDefaultTransitionIndicator) {
96 + this.onDefaultTransitionIndicator = onDefaultTransitionIndicator;
97 + }
98 +
99 this.pooledCache = null;
100 this.pooledCacheLanes = NoLanes;
101
@@ -157,6 +163,7 @@ export function createFiberRoot(
163 // them through the root constructor. Perhaps we should put them all into a
164 // single type, like a DynamicHostConfig that is defined by the renderer.
165 identifierPrefix: string,
166 + formState: ReactFormState<any, any> | null,
167 onUncaughtError: (
168 error: mixed,
169 errorInfo: {+componentStack?: ?string},
@@ -172,8 +179,8 @@ export function createFiberRoot(
179 error: mixed,
180 errorInfo: {+componentStack?: ?string},
181 ) => void,
182 + onDefaultTransitionIndicator: () => void | (() => void),
183 transitionCallbacks: null | TransitionTracingCallbacks,
176 - formState: ReactFormState<any, any> | null,
184 ): FiberRoot {
185 // $FlowFixMe[invalid-constructor] Flow no longer supports calling new on functions
186 const root: FiberRoot = (new FiberRootNode(
@@ -184,6 +191,7 @@ export function createFiberRoot(
191 onUncaughtError,
192 onCaughtError,
193 onRecoverableError,
194 + onDefaultTransitionIndicator,
195 formState,
196 ): any);
197 if (enableSuspenseCallback) {
packages/react-reconciler/src/ReactInternalTypes.js
+2
@@ -280,6 +280,8 @@ type BaseFiberRootProperties = {
280 errorInfo: {+componentStack?: ?string},
281 ) => void,
282
283 + onDefaultTransitionIndicator: () => void | (() => void),
284 +
285 formState: ReactFormState<any, any> | null,
286
287 // enableViewTransition only
packages/react-test-renderer/src/ReactTestRenderer.js
+5
@@ -60,6 +60,10 @@ import {
60 disableLegacyMode,
61 } from 'shared/ReactFeatureFlags';
62
63 +function defaultOnDefaultTransitionIndicator(): void | (() => void) {
64 + // Noop
65 +}
66 +
67 // $FlowFixMe[prop-missing]: This is only in the development export.
68 const act = React.act;
69
@@ -515,6 +519,7 @@ function create(
519 defaultOnUncaughtError,
520 defaultOnCaughtError,
521 defaultOnRecoverableError,
522 + defaultOnDefaultTransitionIndicator,
523 null,
524 );
525
packages/react-test-renderer/src/__tests__/ReactTestRenderer-test.internal.js
+1
@@ -98,6 +98,7 @@ describe('ReactTestRenderer', () => {
98 expect.anything(),
99 expect.anything(),
100 expect.anything(),
101 + expect.anything(),
102 null,
103 );
104 }
packages/shared/ReactFeatureFlags.js
+2
@@ -102,6 +102,8 @@ export const enableSrcObject = __EXPERIMENTAL__;
102
103 export const enableHydrationChangeEvent = __EXPERIMENTAL__;
104
105 +export const enableDefaultTransitionIndicator = __EXPERIMENTAL__;
106 +
107 /**
108 * Switches Fiber creation to a simple object instead of a constructor.
109 */
packages/shared/forks/ReactFeatureFlags.native-fb.js
+1
@@ -85,6 +85,7 @@ export const enableScrollEndPolyfill = true;
85 export const enableSuspenseyImages = false;
86 export const enableSrcObject = false;
87 export const enableHydrationChangeEvent = true;
88 +export const enableDefaultTransitionIndicator = false;
89 export const ownerStackLimit = 1e4;
90
91 // Flow magic to verify the exports of this file match the original version.
packages/shared/forks/ReactFeatureFlags.native-oss.js
+1
@@ -76,6 +76,7 @@ export const enableScrollEndPolyfill = true;
76 export const enableSuspenseyImages = false;
77 export const enableSrcObject = false;
78 export const enableHydrationChangeEvent = false;
79 +export const enableDefaultTransitionIndicator = false;
80 export const ownerStackLimit = 1e4;
81
82 export const enableFragmentRefs = false;
packages/shared/forks/ReactFeatureFlags.test-renderer.js
+1
@@ -75,6 +75,7 @@ export const enableScrollEndPolyfill = true;
75 export const enableSuspenseyImages = false;
76 export const enableSrcObject = false;
77 export const enableHydrationChangeEvent = false;
78 +export const enableDefaultTransitionIndicator = false;
79 export const ownerStackLimit = 1e4;
80
81 export const enableFragmentRefs = false;
packages/shared/forks/ReactFeatureFlags.test-renderer.native-fb.js
+1
@@ -72,6 +72,7 @@ export const enableScrollEndPolyfill = true;
72 export const enableSuspenseyImages = false;
73 export const enableSrcObject = false;
74 export const enableHydrationChangeEvent = false;
75 +export const enableDefaultTransitionIndicator = false;
76 export const enableFragmentRefs = false;
77 export const ownerStackLimit = 1e4;
78
packages/shared/forks/ReactFeatureFlags.test-renderer.www.js
+1
@@ -86,6 +86,7 @@ export const enableScrollEndPolyfill = true;
86 export const enableSuspenseyImages = false;
87 export const enableSrcObject = false;
88 export const enableHydrationChangeEvent = false;
89 +export const enableDefaultTransitionIndicator = false;
90
91 export const enableFragmentRefs = false;
92 export const ownerStackLimit = 1e4;
packages/shared/forks/ReactFeatureFlags.www.js
+1
@@ -115,6 +115,7 @@ export const enableGestureTransition = false;
115 export const enableSuspenseyImages = false;
116 export const enableSrcObject = false;
117 export const enableHydrationChangeEvent = false;
118 +export const enableDefaultTransitionIndicator = false;
119
120 export const ownerStackLimit = 1e4;
121