Files
react/packages/react-devtools-shared/src/devtools/views/Components/OwnersListContext.js
Ruslan Lesiutin 77ec61885f fix[devtools/inspectElement]: dont pause initial inspectElement call when user switches tabs (#27488)
There are not so many changes, most of them are changing imports,
because I've moved types for UI in a single file.

In https://github.com/facebook/react/pull/27357 I've added support for
pausing polling events: when user inspects an element, we start polling
React DevTools backend for updates in props / state. If user switches
tabs, extension's service worker can be killed by browser and this
polling will start spamming errors.

What I've missed is that we also have a separate call for this API, but
which is executed only once when user selects an element. We don't
handle promise rejection here and this can lead to some errors when user
selects an element and switches tabs right after it.

The only change here is that this API now has
`shouldListenToPauseEvents` param, which is `true` for polling, so we
will pause polling once user switches tabs. It is `false` by default, so
we won't pause initial call by accident.


af8beeebf6/packages/react-devtools-shared/src/backendAPI.js (L96)
2023-10-10 18:10:17 +01:00

142 lines
3.9 KiB
JavaScript

/**
* 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 type {ReactContext} from 'shared/ReactTypes';
import * as React from 'react';
import {createContext, useCallback, useContext, useEffect} from 'react';
import {createResource} from '../../cache';
import {BridgeContext, StoreContext} from '../context';
import {TreeStateContext} from './TreeContext';
import {separateDisplayNameAndHOCs} from 'react-devtools-shared/src/utils';
import type {OwnersList} from 'react-devtools-shared/src/backend/types';
import type {
Element,
SerializedElement,
} from 'react-devtools-shared/src/frontend/types';
import type {Resource, Thenable} from '../../cache';
type Context = (id: number) => Array<SerializedElement> | null;
const OwnersListContext: ReactContext<Context> = createContext<Context>(
((null: any): Context),
);
OwnersListContext.displayName = 'OwnersListContext';
type ResolveFn = (ownersList: Array<SerializedElement> | null) => void;
type InProgressRequest = {
promise: Thenable<Array<SerializedElement>>,
resolveFn: ResolveFn,
};
const inProgressRequests: WeakMap<Element, InProgressRequest> = new WeakMap();
const resource: Resource<
Element,
Element,
Array<SerializedElement>,
> = createResource(
(element: Element) => {
const request = inProgressRequests.get(element);
if (request != null) {
return request.promise;
}
let resolveFn:
| ResolveFn
| ((
result: Promise<Array<SerializedElement>> | Array<SerializedElement>,
) => void) = ((null: any): ResolveFn);
const promise = new Promise(resolve => {
resolveFn = resolve;
});
// $FlowFixMe[incompatible-call] found when upgrading Flow
inProgressRequests.set(element, {promise, resolveFn});
return (promise: $FlowFixMe);
},
(element: Element) => element,
{useWeakMap: true},
);
type Props = {
children: React$Node,
};
function OwnersListContextController({children}: Props): React.Node {
const bridge = useContext(BridgeContext);
const store = useContext(StoreContext);
const {ownerID} = useContext(TreeStateContext);
const read = useCallback(
(id: number) => {
const element = store.getElementByID(id);
if (element !== null) {
return resource.read(element);
} else {
return null;
}
},
[store],
);
useEffect(() => {
const onOwnersList = (ownersList: OwnersList) => {
const id = ownersList.id;
const element = store.getElementByID(id);
if (element !== null) {
const request = inProgressRequests.get(element);
if (request != null) {
inProgressRequests.delete(element);
request.resolveFn(
ownersList.owners === null
? null
: ownersList.owners.map(owner => {
const [displayNameWithoutHOCs, hocDisplayNames] =
separateDisplayNameAndHOCs(owner.displayName, owner.type);
return {
...owner,
displayName: displayNameWithoutHOCs,
hocDisplayNames,
};
}),
);
}
}
};
bridge.addListener('ownersList', onOwnersList);
return () => bridge.removeListener('ownersList', onOwnersList);
}, [bridge, store]);
// This effect requests an updated owners list any time the selected owner changes
useEffect(() => {
if (ownerID !== null) {
const rendererID = store.getRendererIDForElement(ownerID);
if (rendererID !== null) {
bridge.send('getOwnersList', {id: ownerID, rendererID});
}
}
return () => {};
}, [bridge, ownerID, store]);
return (
<OwnersListContext.Provider value={read}>
{children}
</OwnersListContext.Provider>
);
}
export {OwnersListContext, OwnersListContextController};