Skip to content

Commit e49335e

Browse files
authored
[DevTools] Display React.optimisticKey in key positions (facebook#35760)
1 parent 57b79b0 commit e49335e

File tree

2 files changed

+148
-4
lines changed

2 files changed

+148
-4
lines changed
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
/**
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*
7+
* @flow
8+
*/
9+
import {getVersionedRenderImplementation} from './utils';
10+
11+
describe('Store React.optimisticKey', () => {
12+
let act;
13+
let actAsync;
14+
let React;
15+
let TestRenderer;
16+
let bridge;
17+
let store;
18+
19+
let BridgeContext;
20+
let StoreContext;
21+
let TreeContext;
22+
23+
let dispatch;
24+
let state;
25+
26+
beforeAll(() => {
27+
// JSDDOM doesn't implement getClientRects so we're just faking one for testing purposes
28+
Element.prototype.getClientRects = function (this: Element) {
29+
const textContent = this.textContent;
30+
return [
31+
new DOMRect(1, 2, textContent.length, textContent.split('\n').length),
32+
];
33+
};
34+
});
35+
36+
beforeEach(() => {
37+
global.IS_REACT_ACT_ENVIRONMENT = true;
38+
39+
store = global.store;
40+
bridge = global.bridge;
41+
42+
React = require('react');
43+
44+
const utils = require('./utils');
45+
act = utils.act;
46+
actAsync = utils.actAsync;
47+
TestRenderer = utils.requireTestRenderer();
48+
49+
BridgeContext =
50+
require('react-devtools-shared/src/devtools/views/context').BridgeContext;
51+
StoreContext =
52+
require('react-devtools-shared/src/devtools/views/context').StoreContext;
53+
TreeContext = require('react-devtools-shared/src/devtools/views/Components/TreeContext');
54+
});
55+
56+
const {render} = getVersionedRenderImplementation();
57+
58+
const Capture = () => {
59+
dispatch = React.useContext(TreeContext.TreeDispatcherContext);
60+
state = React.useContext(TreeContext.TreeStateContext);
61+
return null;
62+
};
63+
64+
const Contexts = () => {
65+
return (
66+
<BridgeContext.Provider value={bridge}>
67+
<StoreContext.Provider value={store}>
68+
<TreeContext.TreeContextController>
69+
<Capture />
70+
</TreeContext.TreeContextController>
71+
</StoreContext.Provider>
72+
</BridgeContext.Provider>
73+
);
74+
};
75+
76+
// @reactVersion >= 19.3
77+
it('is included in the tree', async () => {
78+
if (React.optimisticKey === undefined) {
79+
return;
80+
}
81+
82+
function Component() {
83+
return null;
84+
}
85+
86+
await actAsync(() => {
87+
render(<Component key={React.optimisticKey} />);
88+
});
89+
90+
expect(store).toMatchInlineSnapshot(`
91+
[root]
92+
<Component key="React.optimisticKey">
93+
`);
94+
expect(store.getElementAtIndex(0)).toEqual(
95+
expect.objectContaining({key: 'React.optimisticKey'}),
96+
);
97+
});
98+
99+
// @reactVersion >= 19.3
100+
it('is searchable', async () => {
101+
if (React.optimisticKey === undefined) {
102+
return;
103+
}
104+
await actAsync(() => {
105+
render(<React.Fragment key={React.optimisticKey} />);
106+
});
107+
let renderer;
108+
act(() => (renderer = TestRenderer.create(<Contexts />)));
109+
110+
expect(state).toMatchInlineSnapshot(`
111+
[root]
112+
<Fragment key="React.optimisticKey">
113+
`);
114+
115+
act(() => dispatch({type: 'SET_SEARCH_TEXT', payload: 'optimistic'}));
116+
act(() => renderer.update(<Contexts />));
117+
118+
expect(state).toMatchInlineSnapshot(`
119+
[root]
120+
<Fragment key="React.optimisticKey">
121+
`);
122+
123+
act(() => dispatch({type: 'SET_SEARCH_TEXT', payload: 'react'}));
124+
act(() => renderer.update(<Contexts />));
125+
126+
expect(state).toMatchInlineSnapshot(`
127+
[root]
128+
→ <Fragment key="React.optimisticKey">
129+
`);
130+
});
131+
});

packages/react-devtools-shared/src/backend/fiber/renderer.js

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2606,7 +2606,12 @@ export function attach(
26062606
26072607
// This check is a guard to handle a React element that has been modified
26082608
// in such a way as to bypass the default stringification of the "key" property.
2609-
const keyString = key === null ? null : String(key);
2609+
const keyString =
2610+
key === null
2611+
? null
2612+
: key === REACT_OPTIMISTIC_KEY
2613+
? 'React.optimisticKey'
2614+
: String(key);
26102615
const keyStringID = getStringID(keyString);
26112616
26122617
const nameProp =
@@ -6180,7 +6185,10 @@ export function attach(
61806185
return {
61816186
displayName: getDisplayNameForFiber(fiber) || 'Anonymous',
61826187
id: instance.id,
6183-
key: fiber.key === REACT_OPTIMISTIC_KEY ? null : fiber.key,
6188+
key:
6189+
fiber.key === REACT_OPTIMISTIC_KEY
6190+
? 'React.optimisticKey'
6191+
: fiber.key,
61846192
env: null,
61856193
stack:
61866194
fiber._debugOwner == null || fiber._debugStack == null
@@ -6196,7 +6204,7 @@ export function attach(
61966204
key:
61976205
componentInfo.key == null ||
61986206
componentInfo.key === REACT_OPTIMISTIC_KEY
6199-
? null
6207+
? 'React.optimisticKey'
62006208
: componentInfo.key,
62016209
env: componentInfo.env == null ? null : componentInfo.env,
62026210
stack:
@@ -7123,7 +7131,12 @@ export function attach(
71237131
// Does the component have legacy context attached to it.
71247132
hasLegacyContext,
71257133
7126-
key: key != null && key !== REACT_OPTIMISTIC_KEY ? key : null,
7134+
key:
7135+
key != null
7136+
? key === REACT_OPTIMISTIC_KEY
7137+
? 'React.optimisticKey'
7138+
: key
7139+
: null,
71277140
71287141
type: elementType,
71297142

0 commit comments

Comments
 (0)