Skip to content

Commit

Permalink
V3 - set root.hasUnknownUpdates
Browse files Browse the repository at this point in the history
  • Loading branch information
rickhanlonii committed Jun 8, 2022
1 parent 07acb6e commit 3d41b27
Show file tree
Hide file tree
Showing 21 changed files with 383 additions and 118 deletions.
117 changes: 117 additions & 0 deletions packages/react-dom/src/__tests__/ReactDOMFiberAsync-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -780,6 +780,123 @@ describe('ReactDOMFiberAsync', () => {
expect(Scheduler).toFlushAndYieldThrough(['Count: 2']);
expect(counterRef.current.textContent).toBe('Count: 2');
});

// @gate enableFrameEndScheduling
it('unknown updates should be rescheduled in rAF after a higher priority update', async () => {
let setState = null;
let counterRef = null;
function Counter() {
const [count, setCount] = React.useState(0);
const ref = React.useRef();
setState = setCount;
counterRef = ref;
Scheduler.unstable_yieldValue('Count: ' + count);
return (
<p
ref={ref}
onClick={() => {
setCount(c => c + 1);
}}>
Count: {count}
</p>
);
}

const root = ReactDOMClient.createRoot(container, {
unstable_concurrentUpdatesByDefault: true,
});
act(() => {
root.render(<Counter />);
});
expect(Scheduler).toHaveYielded(['Count: 0']);

window.event = undefined;
setState(1);

// Dispatch a click event on the button.
const firstEvent = document.createEvent('Event');
firstEvent.initEvent('click', true, true);
counterRef.current.dispatchEvent(firstEvent);

await null;

expect(Scheduler).toHaveYielded(['Count: 1']);
expect(counterRef.current.textContent).toBe('Count: 1');

global.flushRequestAnimationFrameQueue();
expect(Scheduler).toHaveYielded(['Count: 2']);
expect(counterRef.current.textContent).toBe('Count: 2');
});

// @gate enableFrameEndScheduling
it('unknown updates should be rescheduled in rAF after a higher priority update', async () => {
let setState = null;
let setThrowing = null;
let counterRef = null;

let promise = null;
let unsuspend = null;

function Counter() {
const [count, setCount] = React.useState(0);
const [isThrowing, setThrowingState] = React.useState(false);
setThrowing = setThrowingState;
const ref = React.useRef();
setState = setCount;
counterRef = ref;
Scheduler.unstable_yieldValue('Count: ' + count);
if (isThrowing) {
if (promise === null) {
promise = new Promise(resolve => {
unsuspend = () => {
resolve();
};
});
}
Scheduler.unstable_yieldValue('suspending');
throw promise;
}
return (
<p
ref={ref}
onClick={() => {
setCount(c => c + 1);
}}>
Count: {count}
</p>
);
}

const root = ReactDOMClient.createRoot(container, {
unstable_concurrentUpdatesByDefault: true,
});
act(() => {
root.render(<Counter />);
});
expect(Scheduler).toHaveYielded(['Count: 0']);

window.event = undefined;
setState(1);

act(() => {
setThrowing(true);
});

expect(Scheduler).toHaveYielded(['Count: 1', 'suspending']);
expect(counterRef.current.textContent).toBe('Count: 0');

act(() => {
unsuspend();
setThrowing(false);

// Should not be scheduled in a rAF.
window.event = 'test';
setState(2);
});

expect(Scheduler).toHaveYielded(['Count: 2']);
expect(counterRef.current.textContent).toBe('Count: 2');
});
});

it('regression test: does not drop passive effects across roots (#17066)', () => {
Expand Down
37 changes: 23 additions & 14 deletions packages/react-dom/src/client/ReactDOMHostConfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,10 +68,11 @@ import {
import {HostComponent, HostText} from 'react-reconciler/src/ReactWorkTags';
import {listenToAllSupportedEvents} from '../events/DOMPluginEventSystem';

import {DefaultEventPriority} from 'react-reconciler/src/ReactEventPriorities';
import {UnknownEventPriority} from 'react-reconciler/src/ReactEventPriorities';

// TODO: Remove this deep import when we delete the legacy root API
import {ConcurrentMode, NoMode} from 'react-reconciler/src/ReactTypeOfMode';
import * as Scheduler from 'scheduler';

export type Type = string;
export type Props = {
Expand Down Expand Up @@ -368,7 +369,7 @@ export function createTextInstance(
export function getCurrentEventPriority(): * {
const currentEvent = window.event;
if (currentEvent === undefined) {
return DefaultEventPriority;
return UnknownEventPriority;
}
return getEventPriority(currentEvent.type);
}
Expand Down Expand Up @@ -411,18 +412,26 @@ export const scheduleMicrotask: any =
// -------------------
// requestAnimationFrame
// -------------------
export const scheduleAnimationFrame: any = localRequestAnimationFrame;
export const cancelAnimationFrame: any = localCancelAnimationFrame;
export const shouldScheduleAnimationFrame =
scheduleAnimationFrame != null && cancelAnimationFrame != null
? function() {
return (
typeof window !== 'undefined' && typeof window.event === 'undefined'
);
}
: function() {
return false;
};
export const supportsFrameEndTask = true;
export function scheduleFrameEndTask(task) {
// Schedule both tasks, we'll race them and use the first to fire.
return {
frameNode: localRequestAnimationFrame(task),
callbackNode: Scheduler.unstable_scheduleCallback(
Scheduler.unstable_NormalPriority,
task,
),
};
}
export function cancelFrameEndTask(task) {
if (task.frameNode != null) {
localCancelAnimationFrame(task.frameNode);
}

if (task.callbackNode != null) {
Scheduler.unstable_cancelCallback(task.callbackNode);
}
}

function handleErrorInNextTick(error) {
setTimeout(() => {
Expand Down
6 changes: 3 additions & 3 deletions packages/react-noop-renderer/src/createReactNoop.js
Original file line number Diff line number Diff line change
Expand Up @@ -434,9 +434,9 @@ function createReactNoop(reconciler: Function, useMutation: boolean) {
})
: setTimeout,

shouldScheduleAnimationFrame: () => false,
scheduleAnimationFrame: undefined,
cancelAnimationFrame: undefined,
supportsFrameEndTask: false,
scheduleFrameEndTask: undefined,
cancelFrameEndTask: undefined,

prepareForCommit(): null | Object {
return null;
Expand Down
13 changes: 6 additions & 7 deletions packages/react-reconciler/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -210,17 +210,16 @@ Set this to true to indicate that your renderer supports `scheduleMicrotask`. We

Optional. You can proxy this to `queueMicrotask` or its equivalent in your environment.

#### `shouldScheduleAnimationFrame`
#### `supportsFrameEndTask`
TODO

Function that returns whether the current event should additionally schedule an animation frame to flush default updated. We use this in React DOM when updates are schedule inside of events that need to flush before the next paint such as ResizeObserver.
### `scheduleFrameEndTask(fn)`

#### `scheduleAnimationFrame(fn)`
TODO

Optional. Function to schedule an animation frame. You can proxy this to `requestAnimationFrame` or its equivalent in your environment.
#### `cancelFrameEndTask(fn)`

#### `cancelAnimationFrame(fn)`

Optional. Function to cancel a scheduled animation frame. You can proxy this to `cancelAnimationFrame` or its equivalent in your environment.
TODO

#### `isPrimaryRenderer`

Expand Down
5 changes: 5 additions & 0 deletions packages/react-reconciler/src/ReactEventPriorities.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import {enableNewReconciler} from 'shared/ReactFeatureFlags';

import {
UnknownEventPriority as UnknownEventPriority_old,
DiscreteEventPriority as DiscreteEventPriority_old,
ContinuousEventPriority as ContinuousEventPriority_old,
DefaultEventPriority as DefaultEventPriority_old,
Expand All @@ -21,6 +22,7 @@ import {
} from './ReactEventPriorities.old';

import {
UnknownEventPriority as UnknownEventPriority_new,
DiscreteEventPriority as DiscreteEventPriority_new,
ContinuousEventPriority as ContinuousEventPriority_new,
DefaultEventPriority as DefaultEventPriority_new,
Expand All @@ -33,6 +35,9 @@ import {

export opaque type EventPriority = number;

export const UnknownEventPriority: EventPriority = enableNewReconciler
? (UnknownEventPriority_new: any)
: (UnknownEventPriority_old: any);
export const DiscreteEventPriority: EventPriority = enableNewReconciler
? (DiscreteEventPriority_new: any)
: (DiscreteEventPriority_old: any);
Expand Down
1 change: 1 addition & 0 deletions packages/react-reconciler/src/ReactEventPriorities.new.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {

export opaque type EventPriority = Lane;

export const UnknownEventPriority: EventPriority = NoLane;
export const DiscreteEventPriority: EventPriority = SyncLane;
export const ContinuousEventPriority: EventPriority = InputContinuousLane;
export const DefaultEventPriority: EventPriority = DefaultLane;
Expand Down
1 change: 1 addition & 0 deletions packages/react-reconciler/src/ReactEventPriorities.old.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {

export opaque type EventPriority = Lane;

export const UnknownEventPriority: EventPriority = NoLane;
export const DiscreteEventPriority: EventPriority = SyncLane;
export const ContinuousEventPriority: EventPriority = InputContinuousLane;
export const DefaultEventPriority: EventPriority = DefaultLane;
Expand Down
29 changes: 25 additions & 4 deletions packages/react-reconciler/src/ReactFiberClassComponent.new.js
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ import {readContext, checkIfContextChanged} from './ReactFiberNewContext.new';
import {
requestEventTime,
requestUpdateLane,
requestUpdateLane_isUnknownEventPriority,
scheduleUpdateOnFiber,
} from './ReactFiberWorkLoop.new';
import {logForceUpdateScheduled, logStateUpdateScheduled} from './DebugTracing';
Expand Down Expand Up @@ -205,7 +206,7 @@ const classComponentUpdater = {
const fiber = getInstance(inst);
const eventTime = requestEventTime();
const lane = requestUpdateLane(fiber);

const isUnknownEventPriority = requestUpdateLane_isUnknownEventPriority();
const update = createUpdate(eventTime, lane);
update.payload = payload;
if (callback !== undefined && callback !== null) {
Expand All @@ -217,7 +218,13 @@ const classComponentUpdater = {

const root = enqueueUpdate(fiber, update, lane);
if (root !== null) {
scheduleUpdateOnFiber(root, fiber, lane, eventTime);
scheduleUpdateOnFiber(
root,
fiber,
lane,
eventTime,
isUnknownEventPriority,
);
entangleTransitions(root, fiber, lane);
}

Expand All @@ -238,6 +245,7 @@ const classComponentUpdater = {
const fiber = getInstance(inst);
const eventTime = requestEventTime();
const lane = requestUpdateLane(fiber);
const isUnknownEventPriority = requestUpdateLane_isUnknownEventPriority();

const update = createUpdate(eventTime, lane);
update.tag = ReplaceState;
Expand All @@ -252,7 +260,13 @@ const classComponentUpdater = {

const root = enqueueUpdate(fiber, update, lane);
if (root !== null) {
scheduleUpdateOnFiber(root, fiber, lane, eventTime);
scheduleUpdateOnFiber(
root,
fiber,
lane,
eventTime,
isUnknownEventPriority,
);
entangleTransitions(root, fiber, lane);
}

Expand All @@ -273,6 +287,7 @@ const classComponentUpdater = {
const fiber = getInstance(inst);
const eventTime = requestEventTime();
const lane = requestUpdateLane(fiber);
const isUnknownEventPriority = requestUpdateLane_isUnknownEventPriority();

const update = createUpdate(eventTime, lane);
update.tag = ForceUpdate;
Expand All @@ -286,7 +301,13 @@ const classComponentUpdater = {

const root = enqueueUpdate(fiber, update, lane);
if (root !== null) {
scheduleUpdateOnFiber(root, fiber, lane, eventTime);
scheduleUpdateOnFiber(
root,
fiber,
lane,
eventTime,
isUnknownEventPriority,
);
entangleTransitions(root, fiber, lane);
}

Expand Down
Loading

0 comments on commit 3d41b27

Please sign in to comment.