Skip to content

Commit 74265ce

Browse files
committed
Allow DevTools to toggle Suspense state
1 parent 80f8b0d commit 74265ce

File tree

5 files changed

+74
-1
lines changed

5 files changed

+74
-1
lines changed

packages/react-debug-tools/src/__tests__/ReactDevToolsHooksIntegration-test.js

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,25 @@ describe('React hooks DevTools integration', () => {
1616
let ReactTestRenderer;
1717
let act;
1818
let overrideHookState;
19+
let overrideProps;
20+
let suspendedFibers;
1921

2022
beforeEach(() => {
23+
suspendedFibers = new Set();
2124
global.__REACT_DEVTOOLS_GLOBAL_HOOK__ = {
2225
inject: injected => {
2326
overrideHookState = injected.overrideHookState;
27+
overrideProps = injected.overrideProps;
2428
},
2529
supportsFiber: true,
2630
onCommitFiberRoot: () => {},
2731
onCommitFiberUnmount: () => {},
32+
shouldSuspendFiber(rendererId, fiber) {
33+
return (
34+
suspendedFibers.has(fiber) ||
35+
(fiber.alternate && suspendedFibers.has(fiber.alternate))
36+
);
37+
},
2838
};
2939

3040
jest.resetModules();
@@ -173,4 +183,41 @@ describe('React hooks DevTools integration', () => {
173183
});
174184
}
175185
});
186+
187+
it('should support triggering suspense in DEV', () => {
188+
function MyComponent() {
189+
return 'Done';
190+
}
191+
192+
const renderer = ReactTestRenderer.create(
193+
<React.Suspense fallback={'Loading'}>
194+
<MyComponent />
195+
</React.Suspense>,
196+
);
197+
expect(renderer.toJSON()).toEqual('Done');
198+
199+
const fiber = renderer.root._currentFiber().return;
200+
if (__DEV__) {
201+
// Mark as loading
202+
suspendedFibers.add(fiber);
203+
overrideProps(fiber, [], null); // Re-render
204+
expect(renderer.toJSON()).toEqual('Loading');
205+
206+
overrideProps(fiber, [], null); // Re-render
207+
expect(renderer.toJSON()).toEqual('Loading');
208+
209+
// Mark as done
210+
suspendedFibers.delete(fiber);
211+
overrideProps(fiber, [], null); // Re-render
212+
expect(renderer.toJSON()).toEqual('Done');
213+
214+
overrideProps(fiber, [], null); // Re-render
215+
expect(renderer.toJSON()).toEqual('Done');
216+
217+
// Mark as loading again
218+
suspendedFibers.add(fiber);
219+
overrideProps(fiber, [], null); // Re-render
220+
expect(renderer.toJSON()).toEqual('Loading');
221+
}
222+
});
176223
});

packages/react-reconciler/src/ReactFiberBeginWork.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ import {
7777
cloneChildFibers,
7878
} from './ReactChildFiber';
7979
import {processUpdateQueue} from './ReactUpdateQueue';
80+
import {shouldSuspend} from './ReactFiberDevToolsHook';
8081
import {
8182
NoWork,
8283
Never,
@@ -1389,6 +1390,12 @@ function updateSuspenseComponent(
13891390
const mode = workInProgress.mode;
13901391
const nextProps = workInProgress.pendingProps;
13911392

1393+
if (__DEV__) {
1394+
if (shouldSuspend(workInProgress)) {
1395+
workInProgress.effectTag |= DidCapture;
1396+
}
1397+
}
1398+
13921399
// We should attempt to render the primary children unless this boundary
13931400
// already suspended during this render (`alreadyCaptured` is true).
13941401
let nextState: SuspenseState | null = workInProgress.memoizedState;

packages/react-reconciler/src/ReactFiberDevToolsHook.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@ declare var __REACT_DEVTOOLS_GLOBAL_HOOK__: Object | void;
1616

1717
let onCommitFiberRoot = null;
1818
let onCommitFiberUnmount = null;
19+
let shouldSuspendFiber = function() {
20+
return false;
21+
};
1922
let hasLoggedError = false;
2023

2124
function catchErrors(fn) {
@@ -71,6 +74,13 @@ export function injectInternals(internals: Object): boolean {
7174
onCommitFiberUnmount = catchErrors(fiber =>
7275
hook.onCommitFiberUnmount(rendererID, fiber),
7376
);
77+
if (__DEV__) {
78+
if (hook.shouldSuspendFiber) {
79+
shouldSuspendFiber = catchErrors(fiber =>
80+
hook.shouldSuspendFiber(rendererID, fiber),
81+
);
82+
}
83+
}
7484
} catch (err) {
7585
// Catch all errors because it is unsafe to throw during initialization.
7686
if (__DEV__) {
@@ -96,3 +106,7 @@ export function onCommitUnmount(fiber: Fiber) {
96106
onCommitFiberUnmount(fiber);
97107
}
98108
}
109+
110+
export function shouldSuspend(fiber: Fiber) {
111+
return shouldSuspendFiber(fiber);
112+
}

packages/react-reconciler/src/ReactFiberReconciler.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -404,7 +404,11 @@ if (__DEV__) {
404404
// Support DevTools props for function components, forwardRef, memo, host components, etc.
405405
overrideProps = (fiber: Fiber, path: Array<string | number>, value: any) => {
406406
flushPassiveEffects();
407-
fiber.pendingProps = copyWithSet(fiber.memoizedProps, path, value);
407+
if (path.length > 0) {
408+
fiber.pendingProps = copyWithSet(fiber.memoizedProps, path, value);
409+
} else {
410+
fiber.pendingProps = {...fiber.pendingProps};
411+
}
408412
if (fiber.alternate) {
409413
fiber.alternate.pendingProps = fiber.pendingProps;
410414
}

packages/react/src/__tests__/ReactProfiler-test.internal.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ const mockDevToolsForTest = () => {
103103
onCommitRoot: () => {},
104104
onCommitUnmount: () => {},
105105
isDevToolsPresent: true,
106+
shouldSuspend: () => false,
106107
}));
107108
};
108109

0 commit comments

Comments
 (0)