diff --git a/packages/react-devtools-shared/src/__tests__/optimisticKeyDevToolsIntegration.js b/packages/react-devtools-shared/src/__tests__/optimisticKeyDevToolsIntegration.js new file mode 100644 index 000000000000..5a6fab25c3c4 --- /dev/null +++ b/packages/react-devtools-shared/src/__tests__/optimisticKeyDevToolsIntegration.js @@ -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 ( + + + + + + + + ); + }; + + // @reactVersion >= 19.3 + it('is included in the tree', async () => { + if (React.optimisticKey === undefined) { + return; + } + + function Component() { + return null; + } + + await actAsync(() => { + render(); + }); + + expect(store).toMatchInlineSnapshot(` + [root] + + `); + 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(); + }); + let renderer; + act(() => (renderer = TestRenderer.create())); + + expect(state).toMatchInlineSnapshot(` + [root] + + `); + + act(() => dispatch({type: 'SET_SEARCH_TEXT', payload: 'optimistic'})); + act(() => renderer.update()); + + expect(state).toMatchInlineSnapshot(` + [root] + + `); + + act(() => dispatch({type: 'SET_SEARCH_TEXT', payload: 'react'})); + act(() => renderer.update()); + + expect(state).toMatchInlineSnapshot(` + [root] + → + `); + }); +}); diff --git a/packages/react-devtools-shared/src/backend/fiber/renderer.js b/packages/react-devtools-shared/src/backend/fiber/renderer.js index 916d69823285..d58247f448f0 100644 --- a/packages/react-devtools-shared/src/backend/fiber/renderer.js +++ b/packages/react-devtools-shared/src/backend/fiber/renderer.js @@ -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 = @@ -6179,7 +6184,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 @@ -6195,7 +6203,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: @@ -7120,7 +7128,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,