Repository: facebook/react
Pull request: [DevTools] Display `React.optimisticKey` in key positions



## Summary

Current display was inconsistent (omitted when inspecting while stringified as `Symbol(react.optimistic_key)` in the tree).

This displays it as `React.optimisticKey` mirroring the export.

## How did you test this change?

- [x] Added test

Changed files:
filename: packages/react-devtools-shared/src/__tests__/optimisticKeyDevToolsIntegration.js
patch: @@ -0,0 +1,131 @@
+/**
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ *
+ * @flow
+ */
+import {getVersionedRenderImplementation} from './utils';
+
+describe('Store React.optimisticKey', () => {
+  let act;
+  let actAsync;
+  let React;
+  let TestRenderer;
+  let bridge;
+  let store;
+
+  let BridgeContext;
+  let StoreContext;
+  let TreeContext;
+
+  let dispatch;
+  let state;
+
+  beforeAll(() => {
+    // JSDDOM doesn't implement getClientRects so we're just faking one for testing purposes
+    Element.prototype.getClientRects = function (this: Element) {
+      const textContent = this.textContent;
+      return [
+        new DOMRect(1, 2, textContent.length, textContent.split('\n').length),
+      ];
+    };
+  });
+
+  beforeEach(() => {
+    global.IS_REACT_ACT_ENVIRONMENT = true;
+
+    store = global.store;
+    bridge = global.bridge;
+
+    React = require('react');
+
+    const utils = require('./utils');
+    act = utils.act;
+    actAsync = utils.actAsync;
+    TestRenderer = utils.requireTestRenderer();
+
+    BridgeContext =
+      require('react-devtools-shared/src/devtools/views/context').BridgeContext;
+    StoreContext =
+      require('react-devtools-shared/src/devtools/views/context').StoreContext;
+    TreeContext = require('react-devtools-shared/src/devtools/views/Components/TreeContext');
+  });
+
+  const {render} = getVersionedRenderImplementation();
+
+  const Capture = () => {
+    dispatch = React.useContext(TreeContext.TreeDispatcherContext);
+    state = React.useContext(TreeContext.TreeStateContext);
+    return null;
+  };
+
+  const Contexts = () => {
+    return (
+      <BridgeContext.Provider value={bridge}>
+        <StoreContext.Provider value={store}>
+          <TreeContext.TreeContextController>
+            <Capture />
+          </TreeContext.TreeContextController>
+        </StoreContext.Provider>
+      </BridgeContext.Provider>
+    );
+  };
+
+  // @reactVersion >= 19.3
+  it('is included in the tree', async () => {
+    if (React.optimisticKey === undefined) {
+      return;
+    }
+
+    function Component() {
+      return null;
+    }
+
+    await actAsync(() => {
+      render(<Component key={React.optimisticKey} />);
+    });
+
+    expect(store).toMatchInlineSnapshot(`
+      [root]
+          <Component key="React.optimisticKey">
+    `);
+    expect(store.getElementAtIndex(0)).toEqual(
+      expect.objectContaining({key: 'React.optimisticKey'}),
+    );
+  });
+
+  // @reactVersion >= 19.3
+  it('is searchable', async () => {
+    if (React.optimisticKey === undefined) {
+      return;
+    }
+    await actAsync(() => {
+      render(<React.Fragment key={React.optimisticKey} />);
+    });
+    let renderer;
+    act(() => (renderer = TestRenderer.create(<Contexts />)));
+
+    expect(state).toMatchInlineSnapshot(`
+      [root]
+           <Fragment key="React.optimisticKey">
+    `);
+
+    act(() => dispatch({type: 'SET_SEARCH_TEXT', payload: 'optimistic'}));
+    act(() => renderer.update(<Contexts />));
+
+    expect(state).toMatchInlineSnapshot(`
+      [root]
+           <Fragment key="React.optimisticKey">
+    `);
+
+    act(() => dispatch({type: 'SET_SEARCH_TEXT', payload: 'react'}));
+    act(() => renderer.update(<Contexts />));
+
+    expect(state).toMatchInlineSnapshot(`
+      [root]
+      →    <Fragment key="React.optimisticKey">
+    `);
+  });
+});

filename: packages/react-devtools-shared/src/backend/fiber/renderer.js
patch: @@ -2606,7 +2606,12 @@ export function attach(
 
       // This check is a guard to handle a React element that has been modified
       // in such a way as to bypass the default stringification of the "key" property.
-      const keyString = key === null ? null : String(key);
+      const keyString =
+        key === null
+          ? null
+          : key === REACT_OPTIMISTIC_KEY
+            ? 'React.optimisticKey'
+            : String(key);
       const keyStringID = getStringID(keyString);
 
       const nameProp =
@@ -6180,7 +6185,10 @@ export function attach(
       return {
         displayName: getDisplayNameForFiber(fiber) || 'Anonymous',
         id: instance.id,
-        key: fiber.key === REACT_OPTIMISTIC_KEY ? null : fiber.key,
+        key:
+          fiber.key === REACT_OPTIMISTIC_KEY
+            ? 'React.optimisticKey'
+            : fiber.key,
         env: null,
         stack:
           fiber._debugOwner == null || fiber._debugStack == null
@@ -6196,7 +6204,7 @@ export function attach(
         key:
           componentInfo.key == null ||
           componentInfo.key === REACT_OPTIMISTIC_KEY
-            ? null
+            ? 'React.optimisticKey'
             : componentInfo.key,
         env: componentInfo.env == null ? null : componentInfo.env,
         stack:
@@ -7123,7 +7131,12 @@ export function attach(
       // Does the component have legacy context attached to it.
       hasLegacyContext,
 
-      key: key != null && key !== REACT_OPTIMISTIC_KEY ? key : null,
+      key:
+        key != null
+          ? key === REACT_OPTIMISTIC_KEY
+            ? 'React.optimisticKey'
+            : key
+          : null,
 
       type: elementType,