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