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
15 changes: 15 additions & 0 deletions packages/react-client/src/__tests__/ReactFlight-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -3884,4 +3884,19 @@ describe('ReactFlight', () => {
</main>,
);
});

// @gate enableOptimisticKey
it('collapses optimistic keys to an optimistic key', async () => {
function Bar({text}) {
return <div />;
}
function Foo() {
return <Bar key={ReactServer.optimisticKey} />;
}
const transport = ReactNoopFlightServer.render({
element: <Foo key="Outer Key" />,
});
const model = await ReactNoopFlightClient.read(transport);
expect(model.element.key).toBe(React.optimisticKey);
});
});
24 changes: 18 additions & 6 deletions packages/react-devtools-shared/src/backend/fiber/renderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ import {
MEMO_SYMBOL_STRING,
SERVER_CONTEXT_SYMBOL_STRING,
LAZY_SYMBOL_STRING,
REACT_OPTIMISTIC_KEY,
} from '../shared/ReactSymbols';
import {enableStyleXFeatures} from 'react-devtools-feature-flags';

Expand Down Expand Up @@ -4849,7 +4850,10 @@ export function attach(
}
let previousSiblingOfBestMatch = null;
let bestMatch = remainingReconcilingChildren;
if (componentInfo.key != null) {
if (
componentInfo.key != null &&
componentInfo.key !== REACT_OPTIMISTIC_KEY
) {
// If there is a key try to find a matching key in the set.
bestMatch = remainingReconcilingChildren;
while (bestMatch !== null) {
Expand Down Expand Up @@ -6145,7 +6149,7 @@ export function attach(
return {
displayName: getDisplayNameForFiber(fiber) || 'Anonymous',
id: instance.id,
key: fiber.key,
key: fiber.key === REACT_OPTIMISTIC_KEY ? null : fiber.key,
env: null,
stack:
fiber._debugOwner == null || fiber._debugStack == null
Expand All @@ -6158,7 +6162,11 @@ export function attach(
return {
displayName: componentInfo.name || 'Anonymous',
id: instance.id,
key: componentInfo.key == null ? null : componentInfo.key,
key:
componentInfo.key == null ||
componentInfo.key === REACT_OPTIMISTIC_KEY
? null
: componentInfo.key,
env: componentInfo.env == null ? null : componentInfo.env,
stack:
componentInfo.owner == null || componentInfo.debugStack == null
Expand Down Expand Up @@ -7082,7 +7090,7 @@ export function attach(
// Does the component have legacy context attached to it.
hasLegacyContext,

key: key != null ? key : null,
key: key != null && key !== REACT_OPTIMISTIC_KEY ? key : null,

type: elementType,

Expand Down Expand Up @@ -8641,15 +8649,19 @@ export function attach(
}
return {
displayName,
key,
key: key === REACT_OPTIMISTIC_KEY ? null : key,
index,
};
}

function getVirtualPathFrame(virtualInstance: VirtualInstance): PathFrame {
return {
displayName: virtualInstance.data.name || '',
key: virtualInstance.data.key == null ? null : virtualInstance.data.key,
key:
virtualInstance.data.key == null ||
virtualInstance.data.key === REACT_OPTIMISTIC_KEY
? null
: virtualInstance.data.key,
index: -1, // We use -1 to indicate that this is a virtual path frame.
};
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,3 +72,9 @@ export const SERVER_CONTEXT_DEFAULT_VALUE_NOT_LOADED_SYMBOL_STRING =
export const REACT_MEMO_CACHE_SENTINEL: symbol = Symbol.for(
'react.memo_cache_sentinel',
);

import type {ReactOptimisticKey} from 'shared/ReactTypes';

export const REACT_OPTIMISTIC_KEY: ReactOptimisticKey = (Symbol.for(
'react.optimistic_key',
): any);
60 changes: 60 additions & 0 deletions packages/react-dom/src/__tests__/ReactDOMFizzStaticBrowser-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -1111,4 +1111,64 @@ describe('ReactDOMFizzStaticBrowser', () => {
</div>,
);
});

// @gate enableHalt && enableOptimisticKey
it('can resume an optimistic keyed slot', async () => {
const errors = [];

let resolve;
const promise = new Promise(r => (resolve = r));

async function Component() {
await promise;
return 'Hi';
}

if (React.optimisticKey === undefined) {
throw new Error('optimisticKey missing');
}

function App() {
return (
<div>
<Suspense fallback="Loading">
<Component key={React.optimisticKey} />
</Suspense>
</div>
);
}

const controller = new AbortController();
const pendingResult = serverAct(() =>
ReactDOMFizzStatic.prerender(<App />, {
signal: controller.signal,
onError(x) {
errors.push(x.message);
},
}),
);

await serverAct(() => {
controller.abort();
});

const prerendered = await pendingResult;

const postponedState = JSON.stringify(prerendered.postponed);

await readIntoContainer(prerendered.prelude);
expect(getVisibleChildren(container)).toEqual(<div>Loading</div>);

expect(prerendered.postponed).not.toBe(null);

await resolve();

const dynamic = await serverAct(() =>
ReactDOMFizzServer.resume(<App />, JSON.parse(postponedState)),
);

await readIntoContainer(dynamic);

expect(getVisibleChildren(container)).toEqual(<div>Hi</div>);
});
});
Loading
Loading