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 {