diff --git a/packages/react-reconciler/src/ReactFiberLane.new.js b/packages/react-reconciler/src/ReactFiberLane.new.js index bb76950eb9735..c3f8f5329ac79 100644 --- a/packages/react-reconciler/src/ReactFiberLane.new.js +++ b/packages/react-reconciler/src/ReactFiberLane.new.js @@ -846,9 +846,9 @@ export function addTransitionToLanesMap( const index = laneToIndex(lane); let transitions = transitionLanesMap[index]; if (transitions === null) { - transitions = []; + transitions = new Set(); } - transitions.push(transition); + transitions.add(transition); transitionLanesMap[index] = transitions; } diff --git a/packages/react-reconciler/src/ReactFiberLane.old.js b/packages/react-reconciler/src/ReactFiberLane.old.js index e71aa5575abb6..49f5d06acbc45 100644 --- a/packages/react-reconciler/src/ReactFiberLane.old.js +++ b/packages/react-reconciler/src/ReactFiberLane.old.js @@ -813,9 +813,9 @@ export function addTransitionToLanesMap( const index = laneToIndex(lane); let transitions = transitionLanesMap[index]; if (transitions === null) { - transitions = []; + transitions = new Set(); } - transitions.push(transition); + transitions.add(transition); transitionLanesMap[index] = transitions; } diff --git a/packages/react-reconciler/src/ReactInternalTypes.js b/packages/react-reconciler/src/ReactInternalTypes.js index abf8a98e99ff3..0f6e9bcaf79ff 100644 --- a/packages/react-reconciler/src/ReactInternalTypes.js +++ b/packages/react-reconciler/src/ReactInternalTypes.js @@ -328,7 +328,7 @@ export type TransitionTracingCallbacks = { // The following fields are only used in transition tracing in Profile builds type TransitionTracingOnlyFiberRootProperties = {| transitionCallbacks: null | TransitionTracingCallbacks, - transitionLanes: Array | null>, + transitionLanes: Array | null>, // Transitions on the root can be represented as a bunch of tracing markers. // Each entangled group of transitions can be treated as a tracing marker. // It will have a set of pending suspense boundaries. These transitions diff --git a/packages/react-reconciler/src/__tests__/ReactTransitionTracing-test.js b/packages/react-reconciler/src/__tests__/ReactTransitionTracing-test.js index 2fd5a3633e426..1926f47fb64d3 100644 --- a/packages/react-reconciler/src/__tests__/ReactTransitionTracing-test.js +++ b/packages/react-reconciler/src/__tests__/ReactTransitionTracing-test.js @@ -212,6 +212,69 @@ describe('ReactInteractionTracing', () => { }); }); + // @gate enableTransitionTracing + it('multiple updates in transition callback should only result in one transitionStart/transitionComplete call', async () => { + const transitionCallbacks = { + onTransitionStart: (name, startTime) => { + Scheduler.unstable_yieldValue( + `onTransitionStart(${name}, ${startTime})`, + ); + }, + onTransitionComplete: (name, startTime, endTime) => { + Scheduler.unstable_yieldValue( + `onTransitionComplete(${name}, ${startTime}, ${endTime})`, + ); + }, + }; + + let navigateToPageTwo; + let setText; + function App() { + const [navigate, setNavigate] = useState(false); + const [text, _setText] = useState('hide'); + navigateToPageTwo = () => setNavigate(true); + setText = () => _setText('show'); + + return ( +
+ {navigate ? ( + + ) : ( + + )} +
+ ); + } + + const root = ReactNoop.createRoot({transitionCallbacks}); + await act(async () => { + root.render(); + ReactNoop.expire(1000); + await advanceTimers(1000); + + expect(Scheduler).toFlushAndYield(['Page One: hide']); + + await act(async () => { + startTransition( + () => { + navigateToPageTwo(); + setText(); + }, + {name: 'page transition'}, + ); + + ReactNoop.expire(1000); + await advanceTimers(1000); + + expect(Scheduler).toFlushAndYield([ + 'Page Two: show', + 'onTransitionStart(page transition, 1000)', + 'onTransitionComplete(page transition, 1000, 2000)', + ]); + }); + }); + }); + // @gate enableTransitionTracing it('should correctly trace interactions for async roots', async () => { const transitionCallbacks = {