@samitouri / QOS-React-1 / commits / 41e9c17a69

fix[devtools]: allow element updates polling only if bridge is alive (#27067)

When some element is inspected in DevTools, we have a polling which updates the data for this element. Sometimes when service worker dies or bridge is getting shutdown, we continue to poll this data and will spam with the same "timed out error". <img width="1728" alt="Screenshot 2023-07-28 at 17 39 23" src="https://github.com/facebook/react/assets/28902667/220c4504-1ccc-4e87-9d78-bfff8b708230"> These changes add an explicit check that polling is allowed only while bridge is alive.

Ruslan Lesiutin committed Aug 29, 2023 at 10:40 UTC 41e9c17a69f819f20a29823a0acf18456c5ef4d2
2 files changed +42 -25
packages/react-devtools-shared/src/devtools/views/Components/InspectedElementContext.js
+15 -1
@@ -107,6 +107,8 @@ export function InspectedElementContextController({
107 parseHookNamesByDefault || alreadyLoadedHookNames,
108 );
109
110 + const [bridgeIsAlive, setBridgeIsAliveStatus] = useState<boolean>(true);
111 +
112 const elementHasChanged = element !== null && element !== state.element;
113
114 // Reset the cached inspected paths when a new element is selected.
@@ -213,14 +215,25 @@ export function InspectedElementContextController({
215 }
216 }, [state]);
217
218 + useEffect(() => {
219 + // Assuming that new bridge is always alive at this moment
220 + setBridgeIsAliveStatus(true);
221 +
222 + const listener = () => setBridgeIsAliveStatus(false);
223 + bridge.addListener('shutdown', listener);
224 +
225 + return () => bridge.removeListener('shutdown', listener);
226 + }, [bridge]);
227 +
228 // Periodically poll the selected element for updates.
229 useEffect(() => {
218 - if (element !== null) {
230 + if (element !== null && bridgeIsAlive) {
231 const checkForUpdateWrapper = () => {
232 checkForUpdate({bridge, element, refresh, store});
233 timeoutID = setTimeout(checkForUpdateWrapper, POLL_INTERVAL);
234 };
235 let timeoutID = setTimeout(checkForUpdateWrapper, POLL_INTERVAL);
236 +
237 return () => {
238 clearTimeout(timeoutID);
239 };
@@ -232,6 +245,7 @@ export function InspectedElementContextController({
245 // No sense to ping right away after e.g. inspecting/hydrating a path.
246 inspectedElement,
247 state,
248 + bridgeIsAlive,
249 ]);
250
251 const value = useMemo<Context>(
packages/react-devtools-shared/src/inspectedElementCache.js
+27 -24
@@ -180,32 +180,35 @@ export function checkForUpdate({
180 }): void {
181 const {id} = element;
182 const rendererID = store.getRendererIDForElement(id);
183 - if (rendererID != null) {
184 - inspectElementMutableSource({
185 - bridge,
186 - element,
187 - path: null,
188 - rendererID: ((rendererID: any): number),
189 - }).then(
190 - ([inspectedElement, responseType]: [
191 - InspectedElementFrontend,
192 - InspectedElementResponseType,
193 - ]) => {
194 - if (responseType === 'full-data') {
195 - startTransition(() => {
196 - const [key, value] = createCacheSeed(element, inspectedElement);
197 - refresh(key, value);
198 - });
199 - }
200 - },
183
202 - // There isn't much to do about errors in this case,
203 - // but we should at least log them so they aren't silent.
204 - error => {
205 - console.error(error);
206 - },
207 - );
184 + if (rendererID == null) {
185 + return;
186 }
187 +
188 + inspectElementMutableSource({
189 + bridge,
190 + element,
191 + path: null,
192 + rendererID,
193 + }).then(
194 + ([inspectedElement, responseType]: [
195 + InspectedElementFrontend,
196 + InspectedElementResponseType,
197 + ]) => {
198 + if (responseType === 'full-data') {
199 + startTransition(() => {
200 + const [key, value] = createCacheSeed(element, inspectedElement);
201 + refresh(key, value);
202 + });
203 + }
204 + },
205 +
206 + // There isn't much to do about errors in this case,
207 + // but we should at least log them so they aren't silent.
208 + error => {
209 + console.error(error);
210 + },
211 + );
212 }
213
214 export function clearCacheBecauseOfError(refresh: RefreshFunction): void {