diff --git a/package.json b/package.json
index eb95ebce230fd..0d50364ef51ee 100644
--- a/package.json
+++ b/package.json
@@ -106,8 +106,8 @@
},
"scripts": {
"build": "node ./scripts/rollup/build.js",
- "build-for-devtools-dev": "cross-env RELEASE_CHANNEL=experimental yarn build react/index,react-dom/index,react-is,react-debug-tools,scheduler,react-test-renderer,react-refresh --type=NODE_DEV",
- "build-for-devtools-prod": "cross-env RELEASE_CHANNEL=experimental yarn build react/index,react-dom/index,react-is,react-debug-tools,scheduler,react-test-renderer,react-refresh --type=NODE_PROD",
+ "build-for-devtools-dev": "cross-env RELEASE_CHANNEL=experimental yarn build react/index,react-dom,react-is,react-debug-tools,scheduler,react-test-renderer,react-refresh --type=NODE_DEV",
+ "build-for-devtools-prod": "cross-env RELEASE_CHANNEL=experimental yarn build react/index,react-dom,react-is,react-debug-tools,scheduler,react-test-renderer,react-refresh --type=NODE_PROD",
"linc": "node ./scripts/tasks/linc.js",
"lint": "node ./scripts/tasks/eslint.js",
"lint-build": "node ./scripts/rollup/validate/index.js",
diff --git a/packages/react-art/package.json b/packages/react-art/package.json
index 194a9697e0a3d..ae977717034e8 100644
--- a/packages/react-art/package.json
+++ b/packages/react-art/package.json
@@ -1,7 +1,7 @@
{
"name": "react-art",
"description": "React ART is a JavaScript library for drawing vector graphics using React. It provides declarative and reactive bindings to the ART library. Using the same declarative API you can render the output to either Canvas, SVG or VML (IE8).",
- "version": "17.0.1",
+ "version": "17.0.2",
"main": "index.js",
"repository": {
"type": "git",
@@ -29,7 +29,7 @@
"scheduler": "^0.20.1"
},
"peerDependencies": {
- "react": "17.0.1"
+ "react": "17.0.2"
},
"files": [
"LICENSE",
diff --git a/packages/react-devtools-shared/src/backend/renderer.js b/packages/react-devtools-shared/src/backend/renderer.js
index 1245c254c5575..f8e0f07104ac9 100644
--- a/packages/react-devtools-shared/src/backend/renderer.js
+++ b/packages/react-devtools-shared/src/backend/renderer.js
@@ -7,7 +7,7 @@
* @flow
*/
-import {gte} from 'semver';
+import {gt, gte} from 'semver';
import {
ComponentFilterDisplayName,
ComponentFilterElementType,
@@ -166,8 +166,10 @@ export function getInternalReactConstants(
// **********************************************************
// The section below is copied from files in React repo.
// Keep it in sync, and add version guards if it changes.
- if (gte(version, '17.0.0-alpha')) {
- // TODO (Offscreen) Update the version number above to reflect the first Offscreen alpha/beta release.
+ //
+ // TODO Update the gt() check below to be gte() whichever the next version number is.
+ // Currently the version in Git is 17.0.2 (but that version has not been/may not end up being released).
+ if (gt(version, '17.0.1')) {
ReactTypeOfWork = {
ClassComponent: 1,
ContextConsumer: 9,
@@ -185,10 +187,41 @@ export function getInternalReactConstants(
IncompleteClassComponent: 17,
IndeterminateComponent: 2,
LazyComponent: 16,
+ LegacyHiddenComponent: 23,
MemoComponent: 14,
Mode: 8,
OffscreenComponent: 22, // Experimental
Profiler: 12,
+ ScopeComponent: 21, // Experimental
+ SimpleMemoComponent: 15,
+ SuspenseComponent: 13,
+ SuspenseListComponent: 19, // Experimental
+ YieldComponent: -1, // Removed
+ };
+ } else if (gte(version, '17.0.0-alpha')) {
+ ReactTypeOfWork = {
+ ClassComponent: 1,
+ ContextConsumer: 9,
+ ContextProvider: 10,
+ CoroutineComponent: -1, // Removed
+ CoroutineHandlerPhase: -1, // Removed
+ DehydratedSuspenseComponent: 18, // Behind a flag
+ ForwardRef: 11,
+ Fragment: 7,
+ FunctionComponent: 0,
+ HostComponent: 5,
+ HostPortal: 4,
+ HostRoot: 3,
+ HostText: 6,
+ IncompleteClassComponent: 17,
+ IndeterminateComponent: 2,
+ LazyComponent: 16,
+ LegacyHiddenComponent: 24,
+ MemoComponent: 14,
+ Mode: 8,
+ OffscreenComponent: 23, // Experimental
+ Profiler: 12,
+ ScopeComponent: 21, // Experimental
SimpleMemoComponent: 15,
SuspenseComponent: 13,
SuspenseListComponent: 19, // Experimental
@@ -212,10 +245,12 @@ export function getInternalReactConstants(
IncompleteClassComponent: 17,
IndeterminateComponent: 2,
LazyComponent: 16,
+ LegacyHiddenComponent: -1,
MemoComponent: 14,
Mode: 8,
OffscreenComponent: -1, // Experimental
Profiler: 12,
+ ScopeComponent: -1, // Experimental
SimpleMemoComponent: 15,
SuspenseComponent: 13,
SuspenseListComponent: 19, // Experimental
@@ -239,10 +274,12 @@ export function getInternalReactConstants(
IncompleteClassComponent: -1, // Doesn't exist yet
IndeterminateComponent: 4,
LazyComponent: -1, // Doesn't exist yet
+ LegacyHiddenComponent: -1,
MemoComponent: -1, // Doesn't exist yet
Mode: 10,
OffscreenComponent: -1, // Experimental
Profiler: 15,
+ ScopeComponent: -1, // Experimental
SimpleMemoComponent: -1, // Doesn't exist yet
SuspenseComponent: 16,
SuspenseListComponent: -1, // Doesn't exist yet
@@ -266,10 +303,12 @@ export function getInternalReactConstants(
IncompleteClassComponent: -1, // Doesn't exist yet
IndeterminateComponent: 0,
LazyComponent: -1, // Doesn't exist yet
+ LegacyHiddenComponent: -1,
MemoComponent: -1, // Doesn't exist yet
Mode: 11,
OffscreenComponent: -1, // Experimental
Profiler: 15,
+ ScopeComponent: -1, // Experimental
SimpleMemoComponent: -1, // Doesn't exist yet
SuspenseComponent: 16,
SuspenseListComponent: -1, // Doesn't exist yet
@@ -301,7 +340,11 @@ export function getInternalReactConstants(
HostPortal,
HostText,
Fragment,
+ LazyComponent,
+ LegacyHiddenComponent,
MemoComponent,
+ OffscreenComponent,
+ ScopeComponent,
SimpleMemoComponent,
SuspenseComponent,
SuspenseListComponent,
@@ -354,11 +397,22 @@ export function getInternalReactConstants(
case HostText:
case Fragment:
return null;
+ case LazyComponent:
+ // This display name will not be user visible.
+ // Once a Lazy component loads its inner component, React replaces the tag and type.
+ // This display name will only show up in console logs when DevTools DEBUG mode is on.
+ return 'Lazy';
case MemoComponent:
case SimpleMemoComponent:
return getDisplayName(resolvedType, 'Anonymous');
case SuspenseComponent:
return 'Suspense';
+ case LegacyHiddenComponent:
+ return 'LegacyHidden';
+ case OffscreenComponent:
+ return 'Offscreen';
+ case ScopeComponent:
+ return 'Scope';
case SuspenseListComponent:
return 'SuspenseList';
default:
@@ -493,10 +547,14 @@ export function attach(
const debug = (name: string, fiber: Fiber, parentFiber: ?Fiber): void => {
if (__DEBUG__) {
- const displayName = getDisplayNameForFiber(fiber) || 'null';
+ const displayName =
+ fiber.tag + ':' + (getDisplayNameForFiber(fiber) || 'null');
const id = getFiberID(fiber);
- const parentDisplayName =
- (parentFiber != null && getDisplayNameForFiber(parentFiber)) || 'null';
+ const parentDisplayName = parentFiber
+ ? parentFiber.tag +
+ ':' +
+ (getDisplayNameForFiber(parentFiber) || 'null')
+ : '';
const parentID = parentFiber ? getFiberID(parentFiber) : '';
// NOTE: calling getFiberID or getPrimaryFiber is unsafe here
// because it will put them in the map. For now, we'll omit them.
@@ -1207,6 +1265,7 @@ export function attach(
return;
}
const id = getFiberID(primaryFiber);
+
if (isRoot) {
// Roots must be removed only after all children (pending and simulated) have been removed.
// So we track it separately.
diff --git a/packages/react-devtools-shared/src/backend/types.js b/packages/react-devtools-shared/src/backend/types.js
index 4ec480acedad7..d87c2fdf97b2e 100644
--- a/packages/react-devtools-shared/src/backend/types.js
+++ b/packages/react-devtools-shared/src/backend/types.js
@@ -42,10 +42,12 @@ export type WorkTagMap = {|
IncompleteClassComponent: WorkTag,
IndeterminateComponent: WorkTag,
LazyComponent: WorkTag,
+ LegacyHiddenComponent: WorkTag,
MemoComponent: WorkTag,
Mode: WorkTag,
OffscreenComponent: WorkTag,
Profiler: WorkTag,
+ ScopeComponent: WorkTag,
SimpleMemoComponent: WorkTag,
SuspenseComponent: WorkTag,
SuspenseListComponent: WorkTag,
diff --git a/packages/react-dom/package.json b/packages/react-dom/package.json
index f512ed9f516b8..5e8991e898772 100644
--- a/packages/react-dom/package.json
+++ b/packages/react-dom/package.json
@@ -1,6 +1,6 @@
{
"name": "react-dom",
- "version": "17.0.1",
+ "version": "17.0.2",
"description": "React package for working with the DOM.",
"main": "index.js",
"repository": {
@@ -22,7 +22,7 @@
"scheduler": "^0.20.1"
},
"peerDependencies": {
- "react": "17.0.1"
+ "react": "17.0.2"
},
"files": [
"LICENSE",
diff --git a/packages/react-is/package.json b/packages/react-is/package.json
index 1687b93fae93b..1a6567dcff5e9 100644
--- a/packages/react-is/package.json
+++ b/packages/react-is/package.json
@@ -1,6 +1,6 @@
{
"name": "react-is",
- "version": "17.0.1",
+ "version": "17.0.2",
"description": "Brand checking of React Elements.",
"main": "index.js",
"repository": {
diff --git a/packages/react-reconciler/package.json b/packages/react-reconciler/package.json
index 858a7190a6eaf..a6fd3a05a9627 100644
--- a/packages/react-reconciler/package.json
+++ b/packages/react-reconciler/package.json
@@ -26,7 +26,7 @@
"node": ">=0.10.0"
},
"peerDependencies": {
- "react": "^17.0.1"
+ "react": "^17.0.2"
},
"dependencies": {
"loose-envify": "^1.1.0",
diff --git a/packages/react-reconciler/src/ReactChildFiber.new.js b/packages/react-reconciler/src/ReactChildFiber.new.js
index 601f3b21e4395..534d7032ba2d9 100644
--- a/packages/react-reconciler/src/ReactChildFiber.new.js
+++ b/packages/react-reconciler/src/ReactChildFiber.new.js
@@ -246,6 +246,12 @@ function warnOnFunctionType(returnFiber: Fiber) {
}
}
+function resolveLazy(lazyType) {
+ const payload = lazyType._payload;
+ const init = lazyType._init;
+ return init(payload);
+}
+
// This wrapper function exists because I expect to clone the code in each path
// to be able to optimize each path individually by branching early. This needs
// a compiler or we can do it manually. Helpers that don't need this branching
@@ -383,11 +389,32 @@ function ChildReconciler(shouldTrackSideEffects) {
element: ReactElement,
lanes: Lanes,
): Fiber {
+ const elementType = element.type;
+ if (elementType === REACT_FRAGMENT_TYPE) {
+ return updateFragment(
+ returnFiber,
+ current,
+ element.props.children,
+ lanes,
+ element.key,
+ );
+ }
if (current !== null) {
if (
- current.elementType === element.type ||
+ current.elementType === elementType ||
// Keep this check inline so it only runs on the false path:
- (__DEV__ ? isCompatibleFamilyForHotReloading(current, element) : false)
+ (__DEV__
+ ? isCompatibleFamilyForHotReloading(current, element)
+ : false) ||
+ // Lazy types should reconcile their resolved type.
+ // We need to do this after the Hot Reloading check above,
+ // because hot reloading has different semantics than prod because
+ // it doesn't resuspend. So we can't let the call below suspend.
+ (enableLazyElements &&
+ typeof elementType === 'object' &&
+ elementType !== null &&
+ elementType.$$typeof === REACT_LAZY_TYPE &&
+ resolveLazy(elementType) === current.type)
) {
// Move based on index
const existing = useFiber(current, element.props);
@@ -551,15 +578,6 @@ function ChildReconciler(shouldTrackSideEffects) {
switch (newChild.$$typeof) {
case REACT_ELEMENT_TYPE: {
if (newChild.key === key) {
- if (newChild.type === REACT_FRAGMENT_TYPE) {
- return updateFragment(
- returnFiber,
- oldFiber,
- newChild.props.children,
- lanes,
- key,
- );
- }
return updateElement(returnFiber, oldFiber, newChild, lanes);
} else {
return null;
@@ -622,15 +640,6 @@ function ChildReconciler(shouldTrackSideEffects) {
existingChildren.get(
newChild.key === null ? newIdx : newChild.key,
) || null;
- if (newChild.type === REACT_FRAGMENT_TYPE) {
- return updateFragment(
- returnFiber,
- matchedFiber,
- newChild.props.children,
- lanes,
- newChild.key,
- );
- }
return updateElement(returnFiber, matchedFiber, newChild, lanes);
}
case REACT_PORTAL_TYPE: {
@@ -1101,39 +1110,44 @@ function ChildReconciler(shouldTrackSideEffects) {
// TODO: If key === null and child.key === null, then this only applies to
// the first item in the list.
if (child.key === key) {
- switch (child.tag) {
- case Fragment: {
- if (element.type === REACT_FRAGMENT_TYPE) {
- deleteRemainingChildren(returnFiber, child.sibling);
- const existing = useFiber(child, element.props.children);
- existing.return = returnFiber;
- if (__DEV__) {
- existing._debugSource = element._source;
- existing._debugOwner = element._owner;
- }
- return existing;
+ const elementType = element.type;
+ if (elementType === REACT_FRAGMENT_TYPE) {
+ if (child.tag === Fragment) {
+ deleteRemainingChildren(returnFiber, child.sibling);
+ const existing = useFiber(child, element.props.children);
+ existing.return = returnFiber;
+ if (__DEV__) {
+ existing._debugSource = element._source;
+ existing._debugOwner = element._owner;
}
- break;
+ return existing;
}
- default: {
- if (
- child.elementType === element.type ||
- // Keep this check inline so it only runs on the false path:
- (__DEV__
- ? isCompatibleFamilyForHotReloading(child, element)
- : false)
- ) {
- deleteRemainingChildren(returnFiber, child.sibling);
- const existing = useFiber(child, element.props);
- existing.ref = coerceRef(returnFiber, child, element);
- existing.return = returnFiber;
- if (__DEV__) {
- existing._debugSource = element._source;
- existing._debugOwner = element._owner;
- }
- return existing;
+ } else {
+ if (
+ child.elementType === elementType ||
+ // Keep this check inline so it only runs on the false path:
+ (__DEV__
+ ? isCompatibleFamilyForHotReloading(child, element)
+ : false) ||
+ // Lazy types should reconcile their resolved type.
+ // We need to do this after the Hot Reloading check above,
+ // because hot reloading has different semantics than prod because
+ // it doesn't resuspend. So we can't let the call below suspend.
+ (enableLazyElements &&
+ typeof elementType === 'object' &&
+ elementType !== null &&
+ elementType.$$typeof === REACT_LAZY_TYPE &&
+ resolveLazy(elementType) === child.type)
+ ) {
+ deleteRemainingChildren(returnFiber, child.sibling);
+ const existing = useFiber(child, element.props);
+ existing.ref = coerceRef(returnFiber, child, element);
+ existing.return = returnFiber;
+ if (__DEV__) {
+ existing._debugSource = element._source;
+ existing._debugOwner = element._owner;
}
- break;
+ return existing;
}
}
// Didn't match.
diff --git a/packages/react-reconciler/src/ReactChildFiber.old.js b/packages/react-reconciler/src/ReactChildFiber.old.js
index adb9c0418df0e..df4c2e10d9299 100644
--- a/packages/react-reconciler/src/ReactChildFiber.old.js
+++ b/packages/react-reconciler/src/ReactChildFiber.old.js
@@ -246,6 +246,12 @@ function warnOnFunctionType(returnFiber: Fiber) {
}
}
+function resolveLazy(lazyType) {
+ const payload = lazyType._payload;
+ const init = lazyType._init;
+ return init(payload);
+}
+
// This wrapper function exists because I expect to clone the code in each path
// to be able to optimize each path individually by branching early. This needs
// a compiler or we can do it manually. Helpers that don't need this branching
@@ -383,11 +389,32 @@ function ChildReconciler(shouldTrackSideEffects) {
element: ReactElement,
lanes: Lanes,
): Fiber {
+ const elementType = element.type;
+ if (elementType === REACT_FRAGMENT_TYPE) {
+ return updateFragment(
+ returnFiber,
+ current,
+ element.props.children,
+ lanes,
+ element.key,
+ );
+ }
if (current !== null) {
if (
- current.elementType === element.type ||
+ current.elementType === elementType ||
// Keep this check inline so it only runs on the false path:
- (__DEV__ ? isCompatibleFamilyForHotReloading(current, element) : false)
+ (__DEV__
+ ? isCompatibleFamilyForHotReloading(current, element)
+ : false) ||
+ // Lazy types should reconcile their resolved type.
+ // We need to do this after the Hot Reloading check above,
+ // because hot reloading has different semantics than prod because
+ // it doesn't resuspend. So we can't let the call below suspend.
+ (enableLazyElements &&
+ typeof elementType === 'object' &&
+ elementType !== null &&
+ elementType.$$typeof === REACT_LAZY_TYPE &&
+ resolveLazy(elementType) === current.type)
) {
// Move based on index
const existing = useFiber(current, element.props);
@@ -551,15 +578,6 @@ function ChildReconciler(shouldTrackSideEffects) {
switch (newChild.$$typeof) {
case REACT_ELEMENT_TYPE: {
if (newChild.key === key) {
- if (newChild.type === REACT_FRAGMENT_TYPE) {
- return updateFragment(
- returnFiber,
- oldFiber,
- newChild.props.children,
- lanes,
- key,
- );
- }
return updateElement(returnFiber, oldFiber, newChild, lanes);
} else {
return null;
@@ -622,15 +640,6 @@ function ChildReconciler(shouldTrackSideEffects) {
existingChildren.get(
newChild.key === null ? newIdx : newChild.key,
) || null;
- if (newChild.type === REACT_FRAGMENT_TYPE) {
- return updateFragment(
- returnFiber,
- matchedFiber,
- newChild.props.children,
- lanes,
- newChild.key,
- );
- }
return updateElement(returnFiber, matchedFiber, newChild, lanes);
}
case REACT_PORTAL_TYPE: {
@@ -1101,39 +1110,43 @@ function ChildReconciler(shouldTrackSideEffects) {
// TODO: If key === null and child.key === null, then this only applies to
// the first item in the list.
if (child.key === key) {
- switch (child.tag) {
- case Fragment: {
- if (element.type === REACT_FRAGMENT_TYPE) {
- deleteRemainingChildren(returnFiber, child.sibling);
- const existing = useFiber(child, element.props.children);
- existing.return = returnFiber;
- if (__DEV__) {
- existing._debugSource = element._source;
- existing._debugOwner = element._owner;
- }
- return existing;
+ const elementType = element.type;
+ if (elementType === REACT_FRAGMENT_TYPE) {
+ if (child.tag === Fragment) {
+ deleteRemainingChildren(returnFiber, child.sibling);
+ const existing = useFiber(child, element.props.children);
+ existing.return = returnFiber;
+ if (__DEV__) {
+ existing._debugSource = element._source;
+ existing._debugOwner = element._owner;
}
- break;
+ return existing;
}
- default: {
- if (
- child.elementType === element.type ||
- // Keep this check inline so it only runs on the false path:
- (__DEV__
- ? isCompatibleFamilyForHotReloading(child, element)
- : false)
- ) {
- deleteRemainingChildren(returnFiber, child.sibling);
- const existing = useFiber(child, element.props);
- existing.ref = coerceRef(returnFiber, child, element);
- existing.return = returnFiber;
- if (__DEV__) {
- existing._debugSource = element._source;
- existing._debugOwner = element._owner;
- }
- return existing;
+ } else {
+ if (
+ child.elementType === elementType ||
+ (__DEV__
+ ? isCompatibleFamilyForHotReloading(child, element)
+ : false) ||
+ // Lazy types should reconcile their resolved type.
+ // We need to do this after the Hot Reloading check above,
+ // because hot reloading has different semantics than prod because
+ // it doesn't resuspend. So we can't let the call below suspend.
+ (enableLazyElements &&
+ typeof elementType === 'object' &&
+ elementType !== null &&
+ elementType.$$typeof === REACT_LAZY_TYPE &&
+ resolveLazy(elementType) === child.type)
+ ) {
+ deleteRemainingChildren(returnFiber, child.sibling);
+ const existing = useFiber(child, element.props);
+ existing.ref = coerceRef(returnFiber, child, element);
+ existing.return = returnFiber;
+ if (__DEV__) {
+ existing._debugSource = element._source;
+ existing._debugOwner = element._owner;
}
- break;
+ return existing;
}
}
// Didn't match.
diff --git a/packages/react-reconciler/src/ReactFiberThrow.new.js b/packages/react-reconciler/src/ReactFiberThrow.new.js
index 9dd9df6d0cfe5..01ab69cecee66 100644
--- a/packages/react-reconciler/src/ReactFiberThrow.new.js
+++ b/packages/react-reconciler/src/ReactFiberThrow.new.js
@@ -256,7 +256,15 @@ function throwException(
// Note: It doesn't matter whether the component that suspended was
// inside a blocking mode tree. If the Suspense is outside of it, we
// should *not* suspend the commit.
- if ((workInProgress.mode & BlockingMode) === NoMode) {
+ //
+ // If the suspense boundary suspended itself suspended, we don't have to
+ // do this trick because nothing was partially started. We can just
+ // directly do a second pass over the fallback in this render and
+ // pretend we meant to render that directly.
+ if (
+ (workInProgress.mode & BlockingMode) === NoMode &&
+ workInProgress !== returnFiber
+ ) {
workInProgress.flags |= DidCapture;
sourceFiber.flags |= ForceUpdateForLegacySuspense;
diff --git a/packages/react-reconciler/src/ReactFiberThrow.old.js b/packages/react-reconciler/src/ReactFiberThrow.old.js
index 8d338d552d7d6..fabcffa87378a 100644
--- a/packages/react-reconciler/src/ReactFiberThrow.old.js
+++ b/packages/react-reconciler/src/ReactFiberThrow.old.js
@@ -256,7 +256,15 @@ function throwException(
// Note: It doesn't matter whether the component that suspended was
// inside a blocking mode tree. If the Suspense is outside of it, we
// should *not* suspend the commit.
- if ((workInProgress.mode & BlockingMode) === NoMode) {
+ //
+ // If the suspense boundary suspended itself suspended, we don't have to
+ // do this trick because nothing was partially started. We can just
+ // directly do a second pass over the fallback in this render and
+ // pretend we meant to render that directly.
+ if (
+ (workInProgress.mode & BlockingMode) === NoMode &&
+ workInProgress !== returnFiber
+ ) {
workInProgress.flags |= DidCapture;
sourceFiber.flags |= ForceUpdateForLegacySuspense;
diff --git a/packages/react-reconciler/src/__tests__/ReactLazy-test.internal.js b/packages/react-reconciler/src/__tests__/ReactLazy-test.internal.js
index 55c15e7412eab..b7f1570823c14 100644
--- a/packages/react-reconciler/src/__tests__/ReactLazy-test.internal.js
+++ b/packages/react-reconciler/src/__tests__/ReactLazy-test.internal.js
@@ -1268,6 +1268,192 @@ describe('ReactLazy', () => {
expect(componentStackMessage).toContain('in Lazy');
});
+ // @gate enableLazyElements
+ it('mount and reorder lazy types', async () => {
+ class Child extends React.Component {
+ componentDidMount() {
+ Scheduler.unstable_yieldValue('Did mount: ' + this.props.label);
+ }
+ componentDidUpdate() {
+ Scheduler.unstable_yieldValue('Did update: ' + this.props.label);
+ }
+ render() {
+ return
Game over
', // TODO: should not have message in prod. ); }); + + // @gate experimental + it('should preserve state of client components on refetch', async () => { + const {Suspense} = React; + + // Client + + function Page({response}) { + return response.readRoot(); + } + + function Input() { + return ; + } + + const InputClient = moduleReference(Input); + + // Server + + function App({color}) { + // Verify both DOM and Client children. + return ( +