Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ describe('React hooks DevTools integration', () => {
let act;
let overrideHookState;
let scheduleUpdate;
let scheduleRetry;
let setSuspenseHandler;
let waitForAll;

Expand All @@ -27,6 +28,7 @@ describe('React hooks DevTools integration', () => {
inject: injected => {
overrideHookState = injected.overrideHookState;
scheduleUpdate = injected.scheduleUpdate;
scheduleRetry = injected.scheduleRetry;
setSuspenseHandler = injected.setSuspenseHandler;
},
supportsFiber: true,
Expand Down Expand Up @@ -312,5 +314,17 @@ describe('React hooks DevTools integration', () => {
} else {
expect(renderer.toJSON().children).toEqual(['Done']);
}

if (scheduleRetry) {
// Lock again, synchronously
setSuspenseHandler(() => true);
await act(() => scheduleUpdate(fiber)); // Re-render
expect(renderer.toJSON().children).toEqual(['Loading']);

// Release the lock again but this time using retry lane
setSuspenseHandler(() => false);
await act(() => scheduleRetry(fiber)); // Re-render
expect(renderer.toJSON().children).toEqual(['Done']);
}
});
});
2 changes: 1 addition & 1 deletion packages/react-devtools-shared/src/__tests__/store-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -838,7 +838,7 @@ describe('Store', () => {
<Suspense name="two" rects={null}>
<Suspense name="three" rects={null}>
`);
await act(() =>
await actAsync(() =>
agent.overrideSuspense({
id: store.getElementIDAtIndex(2),
rendererID,
Expand Down
58 changes: 48 additions & 10 deletions packages/react-devtools-shared/src/backend/fiber/renderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -1065,6 +1065,7 @@ export function attach(
setErrorHandler,
setSuspenseHandler,
scheduleUpdate,
scheduleRetry,
getCurrentFiber,
} = renderer;
const supportsTogglingError =
Expand Down Expand Up @@ -7754,7 +7755,13 @@ export function attach(
// First override is added. Switch React to slower path.
setErrorHandler(shouldErrorFiberAccordingToMap);
}
scheduleUpdate(fiber);
if (!forceError && typeof scheduleRetry === 'function') {
// If we're dismissing an error and the renderer supports it, use a Retry instead of Sync
// This would allow View Transitions to proceed as if the error was dismissed using a Transition.
scheduleRetry(fiber);
} else {
scheduleUpdate(fiber);
}
}

function shouldSuspendFiberAlwaysFalse() {
Expand Down Expand Up @@ -7812,7 +7819,13 @@ export function attach(
setSuspenseHandler(shouldSuspendFiberAlwaysFalse);
}
}
scheduleUpdate(fiber);
if (!forceFallback && typeof scheduleRetry === 'function') {
// If we're unsuspending and the renderer supports it, use a Retry instead of Sync
// to allow for things like View Transitions to proceed the way they would for real.
scheduleRetry(fiber);
} else {
scheduleUpdate(fiber);
}
}

/**
Expand All @@ -7834,11 +7847,10 @@ export function attach(
}

// TODO: Allow overriding the timeline for the specified root.
forceFallbackForFibers.forEach(fiber => {
scheduleUpdate(fiber);
});
forceFallbackForFibers.clear();

const unsuspendedSet: Set<Fiber> = new Set(forceFallbackForFibers);

let resuspended = false;
for (let i = 0; i < suspendedSet.length; ++i) {
const instance = idToDevToolsInstanceMap.get(suspendedSet[i]);
if (instance === undefined) {
Expand All @@ -7850,15 +7862,41 @@ export function attach(

if (instance.kind === FIBER_INSTANCE) {
const fiber = instance.data;
forceFallbackForFibers.add(fiber);
// We could find a minimal set that covers all the Fibers in this suspended set.
// For now we rely on React's batching of updates.
scheduleUpdate(fiber);
if (
forceFallbackForFibers.has(fiber) ||
(fiber.alternate !== null &&
forceFallbackForFibers.has(fiber.alternate))
) {
// We're already forcing fallback for this fiber. Mark it as not unsuspended.
unsuspendedSet.delete(fiber);
if (fiber.alternate !== null) {
unsuspendedSet.delete(fiber.alternate);
}
} else {
forceFallbackForFibers.add(fiber);
// We could find a minimal set that covers all the Fibers in this suspended set.
// For now we rely on React's batching of updates.
scheduleUpdate(fiber);
resuspended = true;
}
} else {
console.warn(`Cannot not suspend ID '${suspendedSet[i]}'.`);
}
}

// Unsuspend any existing forced fallbacks if they're not in the new set.
unsuspendedSet.forEach(fiber => {
forceFallbackForFibers.delete(fiber);
if (!resuspended && typeof scheduleRetry === 'function') {
// If nothing new resuspended we don't need this to be sync. If we're only
// unsuspending then we can schedule this as a Retry if the renderer supports it.
// That way we can trigger animations.
scheduleRetry(fiber);
} else {
scheduleUpdate(fiber);
}
});

if (forceFallbackForFibers.size > 0) {
// First override is added. Switch React to slower path.
// TODO: Semantics for suspending a timeline are different. We want a suspended
Expand Down
2 changes: 2 additions & 0 deletions packages/react-devtools-shared/src/backend/types.js
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,8 @@ export type ReactRenderer = {
) => void,
// 16.9+
scheduleUpdate?: ?(fiber: Object) => void,
// 19.2+
scheduleRetry?: ?(fiber: Object) => void,
setSuspenseHandler?: ?(shouldSuspend: (fiber: Object) => boolean) => void,
// Only injected by React v16.8+ in order to support hooks inspection.
currentDispatcherRef?: LegacyDispatcherRef | CurrentDispatcherRef,
Expand Down
11 changes: 11 additions & 0 deletions packages/react-reconciler/src/ReactFiberReconciler.js
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ import {
getHighestPriorityPendingLanes,
higherPriorityLane,
getBumpedLaneForHydrationByLane,
claimNextRetryLane,
} from './ReactFiberLane';
import {
scheduleRefresh,
Expand Down Expand Up @@ -599,6 +600,7 @@ let overrideProps = null;
let overridePropsDeletePath = null;
let overridePropsRenamePath = null;
let scheduleUpdate = null;
let scheduleRetry = null;
let setErrorHandler = null;
let setSuspenseHandler = null;

Expand Down Expand Up @@ -835,6 +837,14 @@ if (__DEV__) {
}
};

scheduleRetry = (fiber: Fiber) => {
const lane = claimNextRetryLane();
const root = enqueueConcurrentRenderForLane(fiber, lane);
if (root !== null) {
scheduleUpdateOnFiber(root, fiber, lane);
}
};

setErrorHandler = (newShouldErrorImpl: Fiber => ?boolean) => {
shouldErrorImpl = newShouldErrorImpl;
};
Expand Down Expand Up @@ -886,6 +896,7 @@ export function injectIntoDevTools(): boolean {
internals.overridePropsDeletePath = overridePropsDeletePath;
internals.overridePropsRenamePath = overridePropsRenamePath;
internals.scheduleUpdate = scheduleUpdate;
internals.scheduleRetry = scheduleRetry;
internals.setErrorHandler = setErrorHandler;
internals.setSuspenseHandler = setSuspenseHandler;
// React Refresh
Expand Down
Loading