Skip to content

Commit 0b1a9e9

Browse files
authored
Support addTransitionType in startGestureTransition (#32792)
Stacked on #32788. Normally we track `addTransitionType` globally because of the async gap that can happen in Actions where we lack AsyncContext to associate it with a particular Transition. This unfortunately also means it's possible to call outside of `startTransition` which is something we want to warn for. We need to be able to distinguish whether `addTransitionType` is for a regular Transition or a Gesture Transition though. Since `startGestureTransition` is only synchronous we can track it within that execution scope and move it to a separate set. Since we know for sure which call owns it we can properly associate it with that specific provider's `ScheduledGesture`. This does not yet handle calling `addTransitionType` inside the render phase of a gesture. That would currently still be associated with the next Transition instead.
1 parent 8b2046d commit 0b1a9e9

File tree

11 files changed

+99
-17
lines changed

11 files changed

+99
-17
lines changed

fixtures/view-transition/src/components/Page.js

+7-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import React, {
2+
unstable_addTransitionType as addTransitionType,
23
unstable_ViewTransition as ViewTransition,
34
unstable_Activity as Activity,
45
useLayoutEffect,
@@ -113,7 +114,12 @@ export default function Page({url, navigate}) {
113114
<div className="swipe-recognizer">
114115
<SwipeRecognizer
115116
action={swipeAction}
116-
gesture={optimisticNavigate}
117+
gesture={direction => {
118+
addTransitionType(
119+
direction === 'left' ? 'navigation-forward' : 'navigation-back'
120+
);
121+
optimisticNavigate(direction);
122+
}}
117123
direction={show ? 'left' : 'right'}>
118124
<button
119125
className="button"

packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ import type {
2525
PreinitScriptOptions,
2626
PreinitModuleScriptOptions,
2727
} from 'react-dom/src/shared/ReactDOMTypes';
28-
import type {TransitionTypes} from 'react/src/ReactTransitionType.js';
28+
import type {TransitionTypes} from 'react/src/ReactTransitionType';
2929

3030
import {NotPending} from '../shared/ReactDOMFormActions';
3131

packages/react-native-renderer/src/ReactFiberConfigNative.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
*/
99

1010
import type {InspectorData, TouchedViewDataAtPoint} from './ReactNativeTypes';
11-
import type {TransitionTypes} from 'react/src/ReactTransitionType.js';
11+
import type {TransitionTypes} from 'react/src/ReactTransitionType';
1212

1313
// Modules provided by RN:
1414
import {

packages/react-noop-renderer/src/createReactNoop.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import type {UpdateQueue} from 'react-reconciler/src/ReactFiberClassUpdateQueue'
2222
import type {ReactNodeList} from 'shared/ReactTypes';
2323
import type {RootTag} from 'react-reconciler/src/ReactRootTags';
2424
import type {EventPriority} from 'react-reconciler/src/ReactEventPriorities';
25-
import type {TransitionTypes} from 'react/src/ReactTransitionType.js';
25+
import type {TransitionTypes} from 'react/src/ReactTransitionType';
2626

2727
import * as Scheduler from 'scheduler/unstable_mock';
2828
import {REACT_FRAGMENT_TYPE, REACT_ELEMENT_TYPE} from 'shared/ReactSymbols';

packages/react-reconciler/src/ReactFiberGestureScheduler.js

+16
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import type {FiberRoot} from './ReactInternalTypes';
1111
import type {GestureOptions} from 'shared/ReactTypes';
1212
import type {GestureTimeline, RunningViewTransition} from './ReactFiberConfig';
13+
import type {TransitionTypes} from 'react/src/ReactTransitionType';
1314

1415
import {
1516
GestureLane,
@@ -25,6 +26,7 @@ export type ScheduledGesture = {
2526
count: number, // The number of times this same provider has been started.
2627
rangeStart: number, // The percentage along the timeline where the "current" state starts.
2728
rangeEnd: number, // The percentage along the timeline where the "destination" state is reached.
29+
types: null | TransitionTypes, // Any addTransitionType call made during startGestureTransition.
2830
running: null | RunningViewTransition, // Used to cancel the running transition after we're done.
2931
prev: null | ScheduledGesture, // The previous scheduled gesture in the queue for this root.
3032
next: null | ScheduledGesture, // The next scheduled gesture in the queue for this root.
@@ -51,6 +53,7 @@ export function scheduleGesture(
5153
count: 0,
5254
rangeStart: 0, // Uninitialized
5355
rangeEnd: 100, // Uninitialized
56+
types: null,
5457
running: null,
5558
prev: prev,
5659
next: null,
@@ -68,6 +71,7 @@ export function startScheduledGesture(
6871
root: FiberRoot,
6972
gestureTimeline: GestureTimeline,
7073
gestureOptions: ?GestureOptions,
74+
transitionTypes: null | TransitionTypes,
7175
): null | ScheduledGesture {
7276
const rangeStart =
7377
gestureOptions && gestureOptions.rangeStart != null
@@ -87,6 +91,18 @@ export function startScheduledGesture(
8791
// Update the options.
8892
prev.rangeStart = rangeStart;
8993
prev.rangeEnd = rangeEnd;
94+
if (transitionTypes !== null) {
95+
let scheduledTypes = prev.types;
96+
if (scheduledTypes === null) {
97+
scheduledTypes = prev.types = [];
98+
}
99+
for (let i = 0; i < transitionTypes.length; i++) {
100+
const transitionType = transitionTypes[i];
101+
if (scheduledTypes.indexOf(transitionType) === -1) {
102+
scheduledTypes.push(transitionType);
103+
}
104+
}
105+
}
90106
return prev;
91107
}
92108
const next = prev.next;

packages/react-reconciler/src/ReactFiberTransition.js

+9-1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import type {StackCursor} from './ReactFiberStack';
1717
import type {Cache, SpawnedCachePool} from './ReactFiberCacheComponent';
1818
import type {Transition} from 'react/src/ReactStartTransition';
1919
import type {ScheduledGesture} from './ReactFiberGestureScheduler';
20+
import type {TransitionTypes} from 'react/src/ReactTransitionType';
2021

2122
import {
2223
enableTransitionTracing,
@@ -112,13 +113,15 @@ if (enableGestureTransition) {
112113
transition: Transition,
113114
provider: GestureProvider,
114115
options: ?GestureOptions,
116+
transitionTypes: null | TransitionTypes,
115117
): () => void {
116118
let cancel = null;
117119
if (prevOnStartGestureTransitionFinish !== null) {
118120
cancel = prevOnStartGestureTransitionFinish(
119121
transition,
120122
provider,
121123
options,
124+
transitionTypes,
122125
);
123126
}
124127
// For every root that has work scheduled, check if there's a ScheduledGesture
@@ -131,7 +134,12 @@ if (enableGestureTransition) {
131134
// that it's conceptually started globally.
132135
let root = firstScheduledRoot;
133136
while (root !== null) {
134-
const scheduledGesture = startScheduledGesture(root, provider, options);
137+
const scheduledGesture = startScheduledGesture(
138+
root,
139+
provider,
140+
options,
141+
transitionTypes,
142+
);
135143
if (scheduledGesture !== null) {
136144
cancel = chainGestureCancellation(root, scheduledGesture, cancel);
137145
}

packages/react-reconciler/src/ReactFiberWorkLoop.js

+2-3
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ import {
3131
getViewTransitionName,
3232
type ViewTransitionState,
3333
} from './ReactFiberViewTransitionComponent';
34-
import type {TransitionTypes} from 'react/src/ReactTransitionType.js';
34+
import type {TransitionTypes} from 'react/src/ReactTransitionType';
3535

3636
import {
3737
enableCreateEventHandleAPI,
@@ -3925,8 +3925,7 @@ function commitGestureOnRoot(
39253925
setCurrentUpdatePriority(previousPriority);
39263926
ReactSharedInternals.T = prevTransition;
39273927
}
3928-
// TODO: Collect transition types.
3929-
pendingTransitionTypes = null;
3928+
pendingTransitionTypes = finishedGesture.types;
39303929
pendingEffectsStatus = PENDING_GESTURE_MUTATION_PHASE;
39313930

39323931
pendingViewTransition = finishedGesture.running = startGestureTransition(

packages/react-test-renderer/src/ReactFiberConfigTestHost.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
*/
99

1010
import type {ReactContext} from 'shared/ReactTypes';
11-
import type {TransitionTypes} from 'react/src/ReactTransitionType.js';
11+
import type {TransitionTypes} from 'react/src/ReactTransitionType';
1212

1313
import isArray from 'shared/isArray';
1414
import {REACT_CONTEXT_TYPE} from 'shared/ReactSymbols';

packages/react/src/ReactSharedInternalsClient.js

+10-2
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,20 @@ import {
1818
enableGestureTransition,
1919
} from 'shared/ReactFeatureFlags';
2020

21+
type onStartTransitionFinish = (Transition, mixed) => void;
22+
type onStartGestureTransitionFinish = (
23+
Transition,
24+
GestureProvider,
25+
?GestureOptions,
26+
transitionTypes: null | TransitionTypes,
27+
) => () => void;
28+
2129
export type SharedStateClient = {
2230
H: null | Dispatcher, // ReactCurrentDispatcher for Hooks
2331
A: null | AsyncDispatcher, // ReactCurrentCache for Cache
2432
T: null | Transition, // ReactCurrentBatchConfig for Transitions
25-
S: null | ((Transition, mixed) => void), // onStartTransitionFinish
26-
G: null | ((Transition, GestureProvider, ?GestureOptions) => () => void), // onStartGestureTransitionFinish
33+
S: null | onStartTransitionFinish,
34+
G: null | onStartGestureTransitionFinish,
2735
V: null | TransitionTypes, // Pending Transition Types for the Next Transition
2836

2937
// DEV-only

packages/react/src/ReactStartTransition.js

+11
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,12 @@ import {
2121
enableGestureTransition,
2222
} from 'shared/ReactFeatureFlags';
2323

24+
import {
25+
pendingGestureTransitionTypes,
26+
pushPendingGestureTransitionTypes,
27+
popPendingGestureTransitionTypes,
28+
} from './ReactTransitionType';
29+
2430
import reportGlobalError from 'shared/reportGlobalError';
2531

2632
export type Transition = {
@@ -105,6 +111,8 @@ export function startGestureTransition(
105111
}
106112
ReactSharedInternals.T = currentTransition;
107113

114+
const prevTransitionTypes = pushPendingGestureTransitionTypes();
115+
108116
try {
109117
const returnValue = scope();
110118
if (__DEV__) {
@@ -118,17 +126,20 @@ export function startGestureTransition(
118126
);
119127
}
120128
}
129+
const transitionTypes = pendingGestureTransitionTypes;
121130
const onStartGestureTransitionFinish = ReactSharedInternals.G;
122131
if (onStartGestureTransitionFinish !== null) {
123132
return onStartGestureTransitionFinish(
124133
currentTransition,
125134
provider,
126135
options,
136+
transitionTypes,
127137
);
128138
}
129139
} catch (error) {
130140
reportGlobalError(error);
131141
} finally {
142+
popPendingGestureTransitionTypes(prevTransitionTypes);
132143
ReactSharedInternals.T = prevTransition;
133144
}
134145
return function cancelGesture() {

packages/react/src/ReactTransitionType.js

+40-6
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,51 @@
88
*/
99

1010
import ReactSharedInternals from 'shared/ReactSharedInternals';
11-
import {enableViewTransition} from 'shared/ReactFeatureFlags';
11+
import {
12+
enableViewTransition,
13+
enableGestureTransition,
14+
} from 'shared/ReactFeatureFlags';
1215

1316
export type TransitionTypes = Array<string>;
1417

18+
// This one is only available synchronously so we don't need to use ReactSharedInternals
19+
// for this state. Instead, we track it in isomorphic and pass it to the renderer.
20+
export let pendingGestureTransitionTypes: null | TransitionTypes = null;
21+
22+
export function pushPendingGestureTransitionTypes(): null | TransitionTypes {
23+
const prev = pendingGestureTransitionTypes;
24+
pendingGestureTransitionTypes = null;
25+
return prev;
26+
}
27+
28+
export function popPendingGestureTransitionTypes(
29+
prev: null | TransitionTypes,
30+
): void {
31+
pendingGestureTransitionTypes = prev;
32+
}
33+
1534
export function addTransitionType(type: string): void {
1635
if (enableViewTransition) {
17-
const pendingTransitionTypes: null | TransitionTypes =
18-
ReactSharedInternals.V;
19-
if (pendingTransitionTypes === null) {
20-
ReactSharedInternals.V = [type];
21-
} else if (pendingTransitionTypes.indexOf(type) === -1) {
36+
let pendingTransitionTypes: null | TransitionTypes;
37+
if (
38+
enableGestureTransition &&
39+
ReactSharedInternals.T !== null &&
40+
ReactSharedInternals.T.gesture !== null
41+
) {
42+
// We're inside a startGestureTransition which is always sync.
43+
pendingTransitionTypes = pendingGestureTransitionTypes;
44+
if (pendingTransitionTypes === null) {
45+
pendingTransitionTypes = pendingGestureTransitionTypes = [];
46+
}
47+
} else {
48+
// Otherwise we're either inside a synchronous startTransition
49+
// or in the async gap of one, which we track globally.
50+
pendingTransitionTypes = ReactSharedInternals.V;
51+
if (pendingTransitionTypes === null) {
52+
pendingTransitionTypes = ReactSharedInternals.V = [];
53+
}
54+
}
55+
if (pendingTransitionTypes.indexOf(type) === -1) {
2256
pendingTransitionTypes.push(type);
2357
}
2458
}

0 commit comments

Comments
 (0)