Skip to content

Commit

Permalink
Synchronously render the transition lane on popstate
Browse files Browse the repository at this point in the history
  • Loading branch information
tyao1 committed Apr 4, 2023
1 parent 0ae3480 commit 1bceba2
Show file tree
Hide file tree
Showing 13 changed files with 92 additions and 6 deletions.
4 changes: 4 additions & 0 deletions packages/react-art/src/ReactARTHostConfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -346,6 +346,10 @@ export function getCurrentEventPriority() {
return DefaultEventPriority;
}

export function getIsCurrentEventPopState() {
return false;
}

// The ART renderer is secondary to the React DOM renderer.
export const isPrimaryRenderer = false;

Expand Down
4 changes: 4 additions & 0 deletions packages/react-dom-bindings/src/client/ReactDOMHostConfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -540,6 +540,10 @@ export function getCurrentEventPriority(): EventPriority {
return getEventPriority(currentEvent.type);
}

export function getIsCurrentEventPopState(): boolean {
return window.event && window.event.type === 'popstate';
}

export const isPrimaryRenderer = true;
export const warnsIfNotActing = true;
// This initialization code may run even on server environments
Expand Down
41 changes: 41 additions & 0 deletions packages/react-dom/src/__tests__/ReactDOMFiberAsync-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -566,4 +566,45 @@ describe('ReactDOMFiberAsync', () => {

expect(container.textContent).toBe('new');
});

it('should synchronously render the transition lane in a popState', async () => {
function App() {
const [syncState, setSyncState] = React.useState(false);
const [hasNavigated, setHasNavigated] = React.useState(false);
function onPopstate() {
Scheduler.log(`popState`);
React.startTransition(() => {
setHasNavigated(true);
});
setSyncState(true);

// Jest is not emulating window.event correctly in the microtask
const currentEvent = window.event;
window.event = currentEvent;
queueMicrotask(() => {
window.event = null;
});
}
React.useEffect(() => {
window.addEventListener('popstate', onPopstate);
return () => window.removeEventListener('popstate', onPopstate);
}, []);
Scheduler.log(`render:${hasNavigated}/${syncState}`);
return null;
}
const root = ReactDOMClient.createRoot(container);
await act(async () => {
root.render(<App />);
});
assertLog(['render:false/false']);

await act(async () => {
const popStateEvent = new Event('popstate');
window.dispatchEvent(popStateEvent);
});

assertLog(['popState', 'render:true/true']);

root.unmount();
});
});
4 changes: 4 additions & 0 deletions packages/react-native-renderer/src/ReactFabricHostConfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,10 @@ export function getCurrentEventPriority(): * {
return DefaultEventPriority;
}

export function getIsCurrentEventPopState(): boolean {
return false;
}

// The Fabric renderer is secondary to the existing React Native renderer.
export const isPrimaryRenderer = false;

Expand Down
4 changes: 4 additions & 0 deletions packages/react-native-renderer/src/ReactNativeHostConfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,10 @@ export function getCurrentEventPriority(): * {
return DefaultEventPriority;
}

export function getIsCurrentEventPopState(): boolean {
return false;
}

// -------------------
// Mutation
// -------------------
Expand Down
4 changes: 4 additions & 0 deletions packages/react-noop-renderer/src/createReactNoop.js
Original file line number Diff line number Diff line change
Expand Up @@ -531,6 +531,10 @@ function createReactNoop(reconciler: Function, useMutation: boolean) {
return currentEventPriority;
},

getIsCurrentEventPopState(): boolean {
return false;
},

now: Scheduler.unstable_now,

isPrimaryRenderer: true,
Expand Down
4 changes: 2 additions & 2 deletions packages/react-reconciler/src/ReactFiberClassUpdateQueue.js
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ import {
isSubsetOfLanes,
mergeLanes,
removeLanes,
isTransitionLane,
includesTransitionLane,
intersectLanes,
markRootEntangled,
} from './ReactFiberLane';
Expand Down Expand Up @@ -279,7 +279,7 @@ export function entangleTransitions(root: FiberRoot, fiber: Fiber, lane: Lane) {
}

const sharedQueue: SharedQueue<mixed> = (updateQueue: any).shared;
if (isTransitionLane(lane)) {
if (includesTransitionLane(lane)) {
let queueLanes = sharedQueue.lanes;

// If any entangled lanes are no longer pending on the root, then they must
Expand Down
4 changes: 2 additions & 2 deletions packages/react-reconciler/src/ReactFiberHooks.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ import {
mergeLanes,
removeLanes,
intersectLanes,
isTransitionLane,
includesTransitionLane,
markRootEntangled,
markRootMutableRead,
NoTimestamp,
Expand Down Expand Up @@ -2727,7 +2727,7 @@ function entangleTransitionUpdate<S, A>(
queue: UpdateQueue<S, A>,
lane: Lane,
): void {
if (isTransitionLane(lane)) {
if (includesTransitionLane(lane)) {
let queueLanes = queue.lanes;

// If any entangled lanes are no longer pending on the root, then they
Expand Down
6 changes: 5 additions & 1 deletion packages/react-reconciler/src/ReactFiberLane.js
Original file line number Diff line number Diff line change
Expand Up @@ -471,6 +471,10 @@ export function getLanesToRetrySynchronouslyOnError(
return NoLanes;
}

export function getTransitionLanes(lanes: Lanes): Lanes {
return lanes & TransitionLanes;
}

export function includesSyncLane(lanes: Lanes): boolean {
return (lanes & (SyncLane | SyncHydrationLane)) !== NoLanes;
}
Expand Down Expand Up @@ -513,7 +517,7 @@ export function includesExpiredLane(root: FiberRoot, lanes: Lanes): boolean {
return (lanes & root.expiredLanes) !== NoLanes;
}

export function isTransitionLane(lane: Lane): boolean {
export function includesTransitionLane(lane: Lane): boolean {
return (lane & TransitionLanes) !== NoLanes;
}

Expand Down
15 changes: 14 additions & 1 deletion packages/react-reconciler/src/ReactFiberRootScheduler.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,11 @@ import {
getHighestPriorityLane,
getNextLanes,
includesSyncLane,
includesTransitionLane,
markStarvedLanesAsExpired,
markRootEntangled,
mergeLanes,
getTransitionLanes,
} from './ReactFiberLane';
import {
CommitContext,
Expand Down Expand Up @@ -49,7 +53,7 @@ import {
IdleEventPriority,
lanesToEventPriority,
} from './ReactEventPriorities';
import {supportsMicrotasks, scheduleMicrotask} from './ReactFiberHostConfig';
import {supportsMicrotasks, scheduleMicrotask, getIsCurrentEventPopState} from './ReactFiberHostConfig';

import ReactSharedInternals from 'shared/ReactSharedInternals';
const {ReactCurrentActQueue} = ReactSharedInternals;
Expand Down Expand Up @@ -225,10 +229,19 @@ function processRootScheduleInMicrotask() {

const currentTime = now();

let isCurrentEventPopState = null;
let prev = null;
let root = firstScheduledRoot;
while (root !== null) {
const next = root.next;
if (includesTransitionLane(root.pendingLanes)) {
if (isCurrentEventPopState === null) {
isCurrentEventPopState = getIsCurrentEventPopState();
}
if (isCurrentEventPopState) {
markRootEntangled(root, mergeLanes(getTransitionLanes(root.pendingLanes), SyncLane));
}
}
const nextLanes = scheduleTaskForRootDuringMicrotask(root, currentTime);
if (nextLanes === NoLane) {
// This root has no more pending work. Remove it from the schedule. To
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,9 @@ describe('ReactFiberHostContext', () => {
getCurrentEventPriority: function () {
return DefaultEventPriority;
},
getIsCurrentEventPopState() {
return false;
},
requestPostPaintCallback: function () {},
maySuspendCommit(type, props) {
return false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ export const preparePortalMount = $$$hostConfig.preparePortalMount;
export const prepareScopeUpdate = $$$hostConfig.prepareScopeUpdate;
export const getInstanceFromScope = $$$hostConfig.getInstanceFromScope;
export const getCurrentEventPriority = $$$hostConfig.getCurrentEventPriority;
export const getIsCurrentEventPopState =
$$$hostConfig.getIsCurrentEventPopState;
export const detachDeletedInstance = $$$hostConfig.detachDeletedInstance;
export const requestPostPaintCallback = $$$hostConfig.requestPostPaintCallback;
export const maySuspendCommit = $$$hostConfig.maySuspendCommit;
Expand Down
3 changes: 3 additions & 0 deletions packages/react-test-renderer/src/ReactTestHostConfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,9 @@ export function createTextInstance(
export function getCurrentEventPriority(): * {
return DefaultEventPriority;
}
export function getIsCurrentEventPopState(): boolean {
return false;
}

export const isPrimaryRenderer = false;
export const warnsIfNotActing = true;
Expand Down

0 comments on commit 1bceba2

Please sign in to comment.