Skip to content

Commit 578f3f0

Browse files
committed
Allow DevTools to toggle Suspense state
1 parent 80f8b0d commit 578f3f0

File tree

4 files changed

+75
-1
lines changed

4 files changed

+75
-1
lines changed

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

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

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
}

0 commit comments

Comments
 (0)