diff --git a/packages/react-devtools-shared/src/__tests__/FastRefreshDevToolsIntegration-test.js b/packages/react-devtools-shared/src/__tests__/FastRefreshDevToolsIntegration-test.js
new file mode 100644
index 0000000000000..4e2cdecfb9e67
--- /dev/null
+++ b/packages/react-devtools-shared/src/__tests__/FastRefreshDevToolsIntegration-test.js
@@ -0,0 +1,260 @@
+/**
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ *
+ * @flow
+ */
+
+describe('Fast Refresh', () => {
+ let React;
+ let ReactDOM;
+ let ReactFreshRuntime;
+ let act;
+ let babel;
+ let container;
+ let exportsObj;
+ let freshPlugin;
+ let store;
+ let withErrorsOrWarningsIgnored;
+
+ afterEach(() => {
+ jest.resetModules();
+ });
+
+ beforeEach(() => {
+ exportsObj = undefined;
+ container = document.createElement('div');
+
+ babel = require('@babel/core');
+ freshPlugin = require('react-refresh/babel');
+
+ store = global.store;
+
+ React = require('react');
+
+ ReactFreshRuntime = require('react-refresh/runtime');
+ ReactFreshRuntime.injectIntoGlobalHook(global);
+
+ ReactDOM = require('react-dom');
+
+ const utils = require('./utils');
+ act = utils.act;
+ withErrorsOrWarningsIgnored = utils.withErrorsOrWarningsIgnored;
+ });
+
+ function execute(source) {
+ const compiled = babel.transform(source, {
+ babelrc: false,
+ presets: ['@babel/react'],
+ plugins: [
+ [freshPlugin, {skipEnvCheck: true}],
+ '@babel/plugin-transform-modules-commonjs',
+ '@babel/plugin-transform-destructuring',
+ ].filter(Boolean),
+ }).code;
+ exportsObj = {};
+ // eslint-disable-next-line no-new-func
+ new Function(
+ 'global',
+ 'React',
+ 'exports',
+ '$RefreshReg$',
+ '$RefreshSig$',
+ compiled,
+ )(global, React, exportsObj, $RefreshReg$, $RefreshSig$);
+ // Module systems will register exports as a fallback.
+ // This is useful for cases when e.g. a class is exported,
+ // and we don't want to propagate the update beyond this module.
+ $RefreshReg$(exportsObj.default, 'exports.default');
+ return exportsObj.default;
+ }
+
+ function render(source) {
+ const Component = execute(source);
+ act(() => {
+ ReactDOM.render(, container);
+ });
+ // Module initialization shouldn't be counted as a hot update.
+ expect(ReactFreshRuntime.performReactRefresh()).toBe(null);
+ }
+
+ function patch(source) {
+ const prevExports = exportsObj;
+ execute(source);
+ const nextExports = exportsObj;
+
+ // Check if exported families have changed.
+ // (In a real module system we'd do this for *all* exports.)
+ // For example, this can happen if you convert a class to a function.
+ // Or if you wrap something in a HOC.
+ const didExportsChange =
+ ReactFreshRuntime.getFamilyByType(prevExports.default) !==
+ ReactFreshRuntime.getFamilyByType(nextExports.default);
+ if (didExportsChange) {
+ // In a real module system, we would propagate such updates upwards,
+ // and re-execute modules that imported this one. (Just like if we edited them.)
+ // This makes adding/removing/renaming exports re-render references to them.
+ // Here, we'll just force a re-render using the newer type to emulate this.
+ const NextComponent = nextExports.default;
+ act(() => {
+ ReactDOM.render(, container);
+ });
+ }
+ act(() => {
+ const result = ReactFreshRuntime.performReactRefresh();
+ if (!didExportsChange) {
+ // Normally we expect that some components got updated in our tests.
+ expect(result).not.toBe(null);
+ } else {
+ // However, we have tests where we convert functions to classes,
+ // and in those cases it's expected nothing would get updated.
+ // (Instead, the export change branch above would take care of it.)
+ }
+ });
+ expect(ReactFreshRuntime._getMountedRootCount()).toBe(1);
+ }
+
+ function $RefreshReg$(type, id) {
+ ReactFreshRuntime.register(type, id);
+ }
+
+ function $RefreshSig$() {
+ return ReactFreshRuntime.createSignatureFunctionForTransform();
+ }
+
+ it('should not break the DevTools store', () => {
+ render(`
+ function Parent() {
+ return ;
+ };
+
+ function Child() {
+ return
;
+ };
+
+ export default Parent;
+ `);
+ expect(store).toMatchInlineSnapshot(`
+ [root]
+ ▾
+
+ `);
+
+ let element = container.firstChild;
+ expect(container.firstChild).not.toBe(null);
+
+ patch(`
+ function Parent() {
+ return ;
+ };
+
+ function Child() {
+ return ;
+ };
+
+ export default Parent;
+ `);
+ expect(store).toMatchInlineSnapshot(`
+ [root]
+ ▾
+
+ `);
+
+ // State is preserved; this verifies that Fast Refresh is wired up.
+ expect(container.firstChild).toBe(element);
+ element = container.firstChild;
+
+ patch(`
+ function Parent() {
+ return ;
+ };
+
+ function Child() {
+ return ;
+ };
+
+ export default Parent;
+ `);
+ expect(store).toMatchInlineSnapshot(`
+ [root]
+ ▾
+
+ `);
+
+ // State is reset because hooks changed.
+ expect(container.firstChild).not.toBe(element);
+ });
+
+ it('should not break when there are warnings in between patching', () => {
+ withErrorsOrWarningsIgnored(['Expected warning during render'], () => {
+ render(`
+ const {useState} = React;
+
+ export default function Component() {
+ const [state, setState] = useState(1);
+ console.warn("Expected warning during render");
+ return null;
+ }
+ `);
+ });
+ expect(store).toMatchInlineSnapshot(`
+ ✕ 0, ⚠ 1
+ [root]
+ ⚠
+ `);
+
+ withErrorsOrWarningsIgnored(['Expected warning during render'], () => {
+ patch(`
+ const {useEffect, useState} = React;
+
+ export default function Component() {
+ const [state, setState] = useState(1);
+ console.warn("Expected warning during render");
+ return null;
+ }
+ `);
+ });
+ expect(store).toMatchInlineSnapshot(`
+ ✕ 0, ⚠ 2
+ [root]
+ ⚠
+ `);
+
+ withErrorsOrWarningsIgnored(['Expected warning during render'], () => {
+ patch(`
+ const {useEffect, useState} = React;
+
+ export default function Component() {
+ const [state, setState] = useState(1);
+ useEffect(() => {});
+ console.warn("Expected warning during render");
+ return null;
+ }
+ `);
+ });
+ expect(store).toMatchInlineSnapshot(`
+ ✕ 0, ⚠ 1
+ [root]
+ ⚠
+ `);
+
+ withErrorsOrWarningsIgnored(['Expected warning during render'], () => {
+ patch(`
+ const {useEffect, useState} = React;
+
+ export default function Component() {
+ const [state, setState] = useState(1);
+ console.warn("Expected warning during render");
+ return null;
+ }
+ `);
+ });
+ expect(store).toMatchInlineSnapshot(`
+ ✕ 0, ⚠ 1
+ [root]
+ ⚠
+ `);
+ });
+});
diff --git a/packages/react-devtools-shared/src/__tests__/__snapshots__/profilingCache-test.js.snap b/packages/react-devtools-shared/src/__tests__/__snapshots__/profilingCache-test.js.snap
index ad1d487ea790c..8f07adaaf4dae 100644
--- a/packages/react-devtools-shared/src/__tests__/__snapshots__/profilingCache-test.js.snap
+++ b/packages/react-devtools-shared/src/__tests__/__snapshots__/profilingCache-test.js.snap
@@ -10,14 +10,14 @@ Object {
"props": null,
"state": null,
},
- 3 => Object {
+ 4 => Object {
"context": null,
"didHooksChange": false,
"isFirstMount": true,
"props": null,
"state": null,
},
- 5 => Object {
+ 6 => Object {
"context": null,
"didHooksChange": false,
"isFirstMount": true,
@@ -30,14 +30,14 @@ Object {
"fiberActualDurations": Map {
1 => 16,
2 => 16,
- 3 => 1,
- 5 => 1,
+ 4 => 1,
+ 6 => 1,
},
"fiberSelfDurations": Map {
1 => 0,
2 => 10,
- 3 => 1,
- 5 => 1,
+ 4 => 1,
+ 6 => 1,
},
"passiveEffectDuration": null,
"priorityLevel": "Normal",
@@ -104,7 +104,7 @@ Object {
exports[`ProfilingCache should calculate self duration correctly for suspended views: CommitDetails with filtered self durations 2`] = `
Object {
"changeDescriptions": Map {
- 5 => Object {
+ 7 => Object {
"context": null,
"didHooksChange": false,
"isFirstMount": true,
@@ -115,11 +115,11 @@ Object {
"duration": 3,
"effectDuration": null,
"fiberActualDurations": Map {
- 5 => 3,
+ 7 => 3,
3 => 3,
},
"fiberSelfDurations": Map {
- 5 => 3,
+ 7 => 3,
3 => 0,
},
"passiveEffectDuration": null,
@@ -147,21 +147,21 @@ Object {
"props": null,
"state": null,
},
- 3 => Object {
+ 4 => Object {
"context": null,
"didHooksChange": false,
"isFirstMount": true,
"props": null,
"state": null,
},
- 4 => Object {
+ 5 => Object {
"context": null,
"didHooksChange": false,
"isFirstMount": true,
"props": null,
"state": null,
},
- 5 => Object {
+ 6 => Object {
"context": null,
"didHooksChange": false,
"isFirstMount": true,
@@ -174,16 +174,16 @@ Object {
"fiberActualDurations": Map {
1 => 12,
2 => 12,
- 3 => 0,
- 4 => 1,
+ 4 => 0,
5 => 1,
+ 6 => 1,
},
"fiberSelfDurations": Map {
1 => 0,
2 => 10,
- 3 => 0,
- 4 => 1,
+ 4 => 0,
5 => 1,
+ 6 => 1,
},
"passiveEffectDuration": null,
"priorityLevel": "Normal",
@@ -203,21 +203,21 @@ Object {
exports[`ProfilingCache should collect data for each commit: CommitDetails commitIndex: 1 1`] = `
Object {
"changeDescriptions": Map {
- 3 => Object {
+ 4 => Object {
"context": null,
"didHooksChange": false,
"isFirstMount": false,
"props": Array [],
"state": null,
},
- 4 => Object {
+ 5 => Object {
"context": null,
"didHooksChange": false,
"isFirstMount": false,
"props": Array [],
"state": null,
},
- 6 => Object {
+ 7 => Object {
"context": null,
"didHooksChange": false,
"isFirstMount": true,
@@ -237,16 +237,16 @@ Object {
"duration": 13,
"effectDuration": null,
"fiberActualDurations": Map {
- 3 => 0,
- 4 => 1,
- 6 => 2,
+ 4 => 0,
+ 5 => 1,
+ 7 => 2,
2 => 13,
1 => 13,
},
"fiberSelfDurations": Map {
- 3 => 0,
- 4 => 1,
- 6 => 2,
+ 4 => 0,
+ 5 => 1,
+ 7 => 2,
2 => 10,
1 => 0,
},
@@ -268,7 +268,7 @@ Object {
exports[`ProfilingCache should collect data for each commit: CommitDetails commitIndex: 2 1`] = `
Object {
"changeDescriptions": Map {
- 3 => Object {
+ 4 => Object {
"context": null,
"didHooksChange": false,
"isFirstMount": false,
@@ -288,12 +288,12 @@ Object {
"duration": 10,
"effectDuration": null,
"fiberActualDurations": Map {
- 3 => 0,
+ 4 => 0,
2 => 10,
1 => 10,
},
"fiberSelfDurations": Map {
- 3 => 0,
+ 4 => 0,
2 => 10,
1 => 0,
},
@@ -368,7 +368,7 @@ Object {
},
],
Array [
- 3,
+ 4,
Object {
"context": null,
"didHooksChange": false,
@@ -378,7 +378,7 @@ Object {
},
],
Array [
- 4,
+ 5,
Object {
"context": null,
"didHooksChange": false,
@@ -388,7 +388,7 @@ Object {
},
],
Array [
- 5,
+ 6,
Object {
"context": null,
"didHooksChange": false,
@@ -410,15 +410,15 @@ Object {
12,
],
Array [
- 3,
+ 4,
0,
],
Array [
- 4,
+ 5,
1,
],
Array [
- 5,
+ 6,
1,
],
],
@@ -432,15 +432,15 @@ Object {
10,
],
Array [
- 3,
+ 4,
0,
],
Array [
- 4,
+ 5,
1,
],
Array [
- 5,
+ 6,
1,
],
],
@@ -460,7 +460,7 @@ Object {
Object {
"changeDescriptions": Array [
Array [
- 3,
+ 4,
Object {
"context": null,
"didHooksChange": false,
@@ -470,7 +470,7 @@ Object {
},
],
Array [
- 4,
+ 5,
Object {
"context": null,
"didHooksChange": false,
@@ -480,7 +480,7 @@ Object {
},
],
Array [
- 6,
+ 7,
Object {
"context": null,
"didHooksChange": false,
@@ -506,15 +506,15 @@ Object {
"effectDuration": null,
"fiberActualDurations": Array [
Array [
- 3,
+ 4,
0,
],
Array [
- 4,
+ 5,
1,
],
Array [
- 6,
+ 7,
2,
],
Array [
@@ -528,15 +528,15 @@ Object {
],
"fiberSelfDurations": Array [
Array [
- 3,
+ 4,
0,
],
Array [
- 4,
+ 5,
1,
],
Array [
- 6,
+ 7,
2,
],
Array [
@@ -564,7 +564,7 @@ Object {
Object {
"changeDescriptions": Array [
Array [
- 3,
+ 4,
Object {
"context": null,
"didHooksChange": false,
@@ -590,7 +590,7 @@ Object {
"effectDuration": null,
"fiberActualDurations": Array [
Array [
- 3,
+ 4,
0,
],
Array [
@@ -604,7 +604,7 @@ Object {
],
"fiberSelfDurations": Array [
Array [
- 3,
+ 4,
0,
],
Array [
@@ -723,34 +723,34 @@ Object {
2,
12000,
1,
- 3,
+ 4,
5,
2,
2,
2,
3,
4,
- 3,
+ 4,
0,
1,
- 4,
+ 5,
5,
2,
2,
2,
4,
4,
- 4,
+ 5,
1000,
1,
- 5,
+ 6,
8,
2,
2,
2,
0,
4,
- 5,
+ 6,
1000,
],
Array [
@@ -766,14 +766,14 @@ Object {
1,
50,
1,
- 6,
+ 7,
5,
2,
2,
1,
2,
4,
- 6,
+ 7,
2000,
4,
2,
@@ -781,10 +781,10 @@ Object {
3,
2,
4,
- 3,
4,
- 6,
5,
+ 7,
+ 6,
4,
1,
14000,
@@ -795,16 +795,16 @@ Object {
0,
2,
2,
- 6,
- 4,
+ 7,
+ 5,
4,
2,
11000,
3,
2,
2,
- 3,
- 5,
+ 4,
+ 6,
4,
1,
11000,
@@ -815,7 +815,7 @@ Object {
0,
2,
1,
- 3,
+ 4,
],
],
"rootID": 1,
@@ -834,7 +834,7 @@ Array [
]
`;
-exports[`ProfilingCache should collect data for each rendered fiber: FiberCommits: element 3 1`] = `
+exports[`ProfilingCache should collect data for each rendered fiber: FiberCommits: element 4 1`] = `
Array [
0,
1,
@@ -842,20 +842,20 @@ Array [
]
`;
-exports[`ProfilingCache should collect data for each rendered fiber: FiberCommits: element 4 1`] = `
+exports[`ProfilingCache should collect data for each rendered fiber: FiberCommits: element 5 1`] = `
Array [
0,
]
`;
-exports[`ProfilingCache should collect data for each rendered fiber: FiberCommits: element 5 1`] = `
+exports[`ProfilingCache should collect data for each rendered fiber: FiberCommits: element 6 1`] = `
Array [
1,
2,
]
`;
-exports[`ProfilingCache should collect data for each rendered fiber: FiberCommits: element 6 1`] = `
+exports[`ProfilingCache should collect data for each rendered fiber: FiberCommits: element 7 1`] = `
Array [
2,
]
@@ -879,7 +879,7 @@ Object {
},
],
Array [
- 3,
+ 4,
Object {
"context": null,
"didHooksChange": false,
@@ -889,7 +889,7 @@ Object {
},
],
Array [
- 4,
+ 5,
Object {
"context": null,
"didHooksChange": false,
@@ -911,11 +911,11 @@ Object {
11,
],
Array [
- 3,
+ 4,
0,
],
Array [
- 4,
+ 5,
1,
],
],
@@ -929,11 +929,11 @@ Object {
10,
],
Array [
- 3,
+ 4,
0,
],
Array [
- 4,
+ 5,
1,
],
],
@@ -953,7 +953,7 @@ Object {
Object {
"changeDescriptions": Array [
Array [
- 3,
+ 4,
Object {
"context": null,
"didHooksChange": false,
@@ -963,7 +963,7 @@ Object {
},
],
Array [
- 5,
+ 6,
Object {
"context": null,
"didHooksChange": false,
@@ -989,11 +989,11 @@ Object {
"effectDuration": null,
"fiberActualDurations": Array [
Array [
- 3,
+ 4,
0,
],
Array [
- 5,
+ 6,
1,
],
Array [
@@ -1007,11 +1007,11 @@ Object {
],
"fiberSelfDurations": Array [
Array [
- 3,
+ 4,
0,
],
Array [
- 5,
+ 6,
1,
],
Array [
@@ -1039,7 +1039,7 @@ Object {
Object {
"changeDescriptions": Array [
Array [
- 3,
+ 4,
Object {
"context": null,
"didHooksChange": false,
@@ -1049,7 +1049,7 @@ Object {
},
],
Array [
- 5,
+ 6,
Object {
"context": null,
"didHooksChange": false,
@@ -1059,7 +1059,7 @@ Object {
},
],
Array [
- 6,
+ 7,
Object {
"context": null,
"didHooksChange": false,
@@ -1085,15 +1085,15 @@ Object {
"effectDuration": null,
"fiberActualDurations": Array [
Array [
- 3,
+ 4,
0,
],
Array [
- 5,
+ 6,
1,
],
Array [
- 6,
+ 7,
2,
],
Array [
@@ -1107,15 +1107,15 @@ Object {
],
"fiberSelfDurations": Array [
Array [
- 3,
+ 4,
0,
],
Array [
- 5,
+ 6,
1,
],
Array [
- 6,
+ 7,
2,
],
Array [
@@ -1182,24 +1182,24 @@ Object {
2,
11000,
1,
- 3,
+ 4,
5,
2,
2,
2,
3,
4,
- 3,
+ 4,
0,
1,
- 4,
+ 5,
8,
2,
2,
2,
0,
4,
- 4,
+ 5,
1000,
],
Array [
@@ -1215,14 +1215,14 @@ Object {
1,
49,
1,
- 5,
+ 6,
5,
2,
2,
1,
2,
4,
- 5,
+ 6,
1000,
4,
2,
@@ -1230,9 +1230,9 @@ Object {
3,
2,
3,
- 3,
- 5,
4,
+ 6,
+ 5,
4,
1,
12000,
@@ -1250,14 +1250,14 @@ Object {
1,
50,
1,
- 6,
+ 7,
5,
2,
2,
1,
2,
4,
- 6,
+ 7,
2000,
4,
2,
@@ -1265,10 +1265,10 @@ Object {
3,
2,
4,
- 3,
- 5,
- 6,
4,
+ 6,
+ 7,
+ 5,
4,
1,
14000,
@@ -1287,21 +1287,21 @@ Object {
"commitData": Array [
Object {
"changeDescriptions": Map {
- 3 => Object {
+ 4 => Object {
"context": null,
"didHooksChange": false,
"isFirstMount": false,
"props": Array [],
"state": null,
},
- 4 => Object {
+ 5 => Object {
"context": null,
"didHooksChange": false,
"isFirstMount": false,
"props": Array [],
"state": null,
},
- 10 => Object {
+ 12 => Object {
"context": null,
"didHooksChange": false,
"isFirstMount": true,
@@ -1321,16 +1321,16 @@ Object {
"duration": 13,
"effectDuration": null,
"fiberActualDurations": Map {
- 3 => 0,
- 4 => 1,
- 10 => 2,
+ 4 => 0,
+ 5 => 1,
+ 12 => 2,
2 => 13,
1 => 13,
},
"fiberSelfDurations": Map {
- 3 => 0,
- 4 => 1,
- 10 => 2,
+ 4 => 0,
+ 5 => 1,
+ 12 => 2,
2 => 10,
1 => 0,
},
@@ -1349,7 +1349,7 @@ Object {
},
Object {
"changeDescriptions": Map {
- 3 => Object {
+ 4 => Object {
"context": null,
"didHooksChange": false,
"isFirstMount": false,
@@ -1369,12 +1369,12 @@ Object {
"duration": 10,
"effectDuration": null,
"fiberActualDurations": Map {
- 3 => 0,
+ 4 => 0,
2 => 10,
1 => 10,
},
"fiberSelfDurations": Map {
- 3 => 0,
+ 4 => 0,
2 => 10,
1 => 0,
},
@@ -1431,9 +1431,9 @@ Object {
"initialTreeBaseDurations": Map {
1 => 12,
2 => 12,
- 3 => 0,
- 4 => 1,
+ 4 => 0,
5 => 1,
+ 6 => 1,
},
"operations": Array [
Array [
@@ -1449,14 +1449,14 @@ Object {
1,
50,
1,
- 10,
+ 12,
5,
2,
2,
1,
2,
4,
- 10,
+ 12,
2000,
4,
2,
@@ -1464,10 +1464,10 @@ Object {
3,
2,
4,
- 3,
4,
- 10,
5,
+ 12,
+ 6,
4,
1,
14000,
@@ -1478,16 +1478,16 @@ Object {
0,
2,
2,
- 10,
- 4,
+ 12,
+ 5,
4,
2,
11000,
3,
2,
2,
- 3,
- 5,
+ 4,
+ 6,
4,
1,
11000,
@@ -1498,7 +1498,7 @@ Object {
0,
2,
1,
- 3,
+ 4,
],
],
"rootID": 1,
@@ -1515,9 +1515,9 @@ Object {
},
2 => Object {
"children": Array [
- 3,
4,
5,
+ 6,
],
"displayName": "Parent",
"hocDisplayNames": null,
@@ -1525,29 +1525,29 @@ Object {
"key": null,
"type": 5,
},
- 3 => Object {
+ 4 => Object {
"children": Array [],
"displayName": "Child",
"hocDisplayNames": null,
- "id": 3,
+ "id": 4,
"key": "0",
"type": 5,
},
- 4 => Object {
+ 5 => Object {
"children": Array [],
"displayName": "Child",
"hocDisplayNames": null,
- "id": 4,
+ "id": 5,
"key": "1",
"type": 5,
},
- 5 => Object {
+ 6 => Object {
"children": Array [],
"displayName": "Child",
"hocDisplayNames": Array [
"Memo",
],
- "id": 5,
+ "id": 6,
"key": null,
"type": 8,
},
@@ -1560,21 +1560,21 @@ Object {
"commitData": Array [
Object {
"changeDescriptions": Map {
- 12 => Object {
+ 14 => Object {
"context": null,
"didHooksChange": false,
"isFirstMount": true,
"props": null,
"state": null,
},
- 13 => Object {
+ 16 => Object {
"context": null,
"didHooksChange": false,
"isFirstMount": true,
"props": null,
"state": null,
},
- 14 => Object {
+ 17 => Object {
"context": null,
"didHooksChange": false,
"isFirstMount": true,
@@ -1585,16 +1585,16 @@ Object {
"duration": 11,
"effectDuration": null,
"fiberActualDurations": Map {
- 11 => 11,
- 12 => 11,
- 13 => 0,
- 14 => 1,
+ 13 => 11,
+ 14 => 11,
+ 16 => 0,
+ 17 => 1,
},
"fiberSelfDurations": Map {
- 11 => 0,
- 12 => 10,
13 => 0,
- 14 => 1,
+ 14 => 10,
+ 16 => 0,
+ 17 => 1,
},
"passiveEffectDuration": null,
"priorityLevel": "Normal",
@@ -1603,7 +1603,7 @@ Object {
Object {
"displayName": "Anonymous",
"hocDisplayNames": null,
- "id": 11,
+ "id": 13,
"key": null,
"type": 11,
},
@@ -1615,7 +1615,7 @@ Object {
"operations": Array [
Array [
1,
- 11,
+ 13,
15,
6,
80,
@@ -1633,46 +1633,46 @@ Object {
1,
48,
1,
- 11,
+ 13,
11,
1,
1,
4,
- 11,
+ 13,
11000,
1,
- 12,
+ 14,
5,
- 11,
+ 13,
0,
1,
0,
4,
- 12,
+ 14,
11000,
1,
- 13,
+ 16,
5,
- 12,
- 12,
+ 14,
+ 14,
2,
3,
4,
- 13,
+ 16,
0,
1,
- 14,
+ 17,
8,
- 12,
- 12,
+ 14,
+ 14,
2,
0,
4,
- 14,
+ 17,
1000,
],
],
- "rootID": 11,
+ "rootID": 13,
"snapshots": Map {},
}
`;
@@ -1693,7 +1693,7 @@ Object {
Object {
"displayName": "Anonymous",
"hocDisplayNames": null,
- "id": 6,
+ "id": 7,
"key": null,
"type": 11,
},
@@ -1702,62 +1702,62 @@ Object {
],
"displayName": "Parent",
"initialTreeBaseDurations": Map {
- 6 => 11,
7 => 11,
- 8 => 0,
- 9 => 1,
+ 8 => 11,
+ 10 => 0,
+ 11 => 1,
},
"operations": Array [
Array [
1,
- 6,
+ 7,
0,
2,
4,
- 9,
+ 11,
+ 10,
8,
7,
- 6,
],
],
- "rootID": 6,
+ "rootID": 7,
"snapshots": Map {
- 6 => Object {
+ 7 => Object {
"children": Array [
- 7,
+ 8,
],
"displayName": null,
"hocDisplayNames": null,
- "id": 6,
+ "id": 7,
"key": null,
"type": 11,
},
- 7 => Object {
+ 8 => Object {
"children": Array [
- 8,
- 9,
+ 10,
+ 11,
],
"displayName": "Parent",
"hocDisplayNames": null,
- "id": 7,
+ "id": 8,
"key": null,
"type": 5,
},
- 8 => Object {
+ 10 => Object {
"children": Array [],
"displayName": "Child",
"hocDisplayNames": null,
- "id": 8,
+ "id": 10,
"key": "0",
"type": 5,
},
- 9 => Object {
+ 11 => Object {
"children": Array [],
"displayName": "Child",
"hocDisplayNames": Array [
"Memo",
],
- "id": 9,
+ "id": 11,
"key": null,
"type": 8,
},
@@ -1773,7 +1773,7 @@ Object {
Object {
"changeDescriptions": Array [
Array [
- 3,
+ 4,
Object {
"context": null,
"didHooksChange": false,
@@ -1783,7 +1783,7 @@ Object {
},
],
Array [
- 4,
+ 5,
Object {
"context": null,
"didHooksChange": false,
@@ -1793,7 +1793,7 @@ Object {
},
],
Array [
- 10,
+ 12,
Object {
"context": null,
"didHooksChange": false,
@@ -1819,15 +1819,15 @@ Object {
"effectDuration": null,
"fiberActualDurations": Array [
Array [
- 3,
+ 4,
0,
],
Array [
- 4,
+ 5,
1,
],
Array [
- 10,
+ 12,
2,
],
Array [
@@ -1841,15 +1841,15 @@ Object {
],
"fiberSelfDurations": Array [
Array [
- 3,
+ 4,
0,
],
Array [
- 4,
+ 5,
1,
],
Array [
- 10,
+ 12,
2,
],
Array [
@@ -1877,7 +1877,7 @@ Object {
Object {
"changeDescriptions": Array [
Array [
- 3,
+ 4,
Object {
"context": null,
"didHooksChange": false,
@@ -1903,7 +1903,7 @@ Object {
"effectDuration": null,
"fiberActualDurations": Array [
Array [
- 3,
+ 4,
0,
],
Array [
@@ -1917,7 +1917,7 @@ Object {
],
"fiberSelfDurations": Array [
Array [
- 3,
+ 4,
0,
],
Array [
@@ -2004,15 +2004,15 @@ Object {
12,
],
Array [
- 3,
+ 4,
0,
],
Array [
- 4,
+ 5,
1,
],
Array [
- 5,
+ 6,
1,
],
],
@@ -2030,14 +2030,14 @@ Object {
1,
50,
1,
- 10,
+ 12,
5,
2,
2,
1,
2,
4,
- 10,
+ 12,
2000,
4,
2,
@@ -2045,10 +2045,10 @@ Object {
3,
2,
4,
- 3,
4,
- 10,
5,
+ 12,
+ 6,
4,
1,
14000,
@@ -2059,16 +2059,16 @@ Object {
0,
2,
2,
- 10,
- 4,
+ 12,
+ 5,
4,
2,
11000,
3,
2,
2,
- 3,
- 5,
+ 4,
+ 6,
4,
1,
11000,
@@ -2079,7 +2079,7 @@ Object {
0,
2,
1,
- 3,
+ 4,
],
],
"rootID": 1,
@@ -2101,9 +2101,9 @@ Object {
2,
Object {
"children": Array [
- 3,
4,
5,
+ 6,
],
"displayName": "Parent",
"hocDisplayNames": null,
@@ -2113,36 +2113,36 @@ Object {
},
],
Array [
- 3,
+ 4,
Object {
"children": Array [],
"displayName": "Child",
"hocDisplayNames": null,
- "id": 3,
+ "id": 4,
"key": "0",
"type": 5,
},
],
Array [
- 4,
+ 5,
Object {
"children": Array [],
"displayName": "Child",
"hocDisplayNames": null,
- "id": 4,
+ "id": 5,
"key": "1",
"type": 5,
},
],
Array [
- 5,
+ 6,
Object {
"children": Array [],
"displayName": "Child",
"hocDisplayNames": Array [
"Memo",
],
- "id": 5,
+ "id": 6,
"key": null,
"type": 8,
},
@@ -2154,7 +2154,7 @@ Object {
Object {
"changeDescriptions": Array [
Array [
- 12,
+ 14,
Object {
"context": null,
"didHooksChange": false,
@@ -2164,7 +2164,7 @@ Object {
},
],
Array [
- 13,
+ 16,
Object {
"context": null,
"didHooksChange": false,
@@ -2174,7 +2174,7 @@ Object {
},
],
Array [
- 14,
+ 17,
Object {
"context": null,
"didHooksChange": false,
@@ -2188,37 +2188,37 @@ Object {
"effectDuration": null,
"fiberActualDurations": Array [
Array [
- 11,
+ 13,
11,
],
Array [
- 12,
+ 14,
11,
],
Array [
- 13,
+ 16,
0,
],
Array [
- 14,
+ 17,
1,
],
],
"fiberSelfDurations": Array [
Array [
- 11,
+ 13,
0,
],
Array [
- 12,
+ 14,
10,
],
Array [
- 13,
+ 16,
0,
],
Array [
- 14,
+ 17,
1,
],
],
@@ -2229,7 +2229,7 @@ Object {
Object {
"displayName": "Anonymous",
"hocDisplayNames": null,
- "id": 11,
+ "id": 13,
"key": null,
"type": 11,
},
@@ -2241,7 +2241,7 @@ Object {
"operations": Array [
Array [
1,
- 11,
+ 13,
15,
6,
80,
@@ -2259,46 +2259,46 @@ Object {
1,
48,
1,
- 11,
+ 13,
11,
1,
1,
4,
- 11,
+ 13,
11000,
1,
- 12,
+ 14,
5,
- 11,
+ 13,
0,
1,
0,
4,
- 12,
+ 14,
11000,
1,
- 13,
+ 16,
5,
- 12,
- 12,
+ 14,
+ 14,
2,
3,
4,
- 13,
+ 16,
0,
1,
- 14,
+ 17,
8,
- 12,
- 12,
+ 14,
+ 14,
2,
0,
4,
- 14,
+ 17,
1000,
],
],
- "rootID": 11,
+ "rootID": 13,
"snapshots": Array [],
},
Object {
@@ -2316,7 +2316,7 @@ Object {
Object {
"displayName": "Anonymous",
"hocDisplayNames": null,
- "id": 6,
+ "id": 7,
"key": null,
"type": 11,
},
@@ -2326,84 +2326,84 @@ Object {
"displayName": "Parent",
"initialTreeBaseDurations": Array [
Array [
- 6,
+ 7,
11,
],
Array [
- 7,
+ 8,
11,
],
Array [
- 8,
+ 10,
0,
],
Array [
- 9,
+ 11,
1,
],
],
"operations": Array [
Array [
1,
- 6,
+ 7,
0,
2,
4,
- 9,
+ 11,
+ 10,
8,
7,
- 6,
],
],
- "rootID": 6,
+ "rootID": 7,
"snapshots": Array [
Array [
- 6,
+ 7,
Object {
"children": Array [
- 7,
+ 8,
],
"displayName": null,
"hocDisplayNames": null,
- "id": 6,
+ "id": 7,
"key": null,
"type": 11,
},
],
Array [
- 7,
+ 8,
Object {
"children": Array [
- 8,
- 9,
+ 10,
+ 11,
],
"displayName": "Parent",
"hocDisplayNames": null,
- "id": 7,
+ "id": 8,
"key": null,
"type": 5,
},
],
Array [
- 8,
+ 10,
Object {
"children": Array [],
"displayName": "Child",
"hocDisplayNames": null,
- "id": 8,
+ "id": 10,
"key": "0",
"type": 5,
},
],
Array [
- 9,
+ 11,
Object {
"children": Array [],
"displayName": "Child",
"hocDisplayNames": Array [
"Memo",
],
- "id": 9,
+ "id": 11,
"key": null,
"type": 8,
},
diff --git a/packages/react-devtools-shared/src/__tests__/__snapshots__/profilingCommitTreeBuilder-test.js.snap b/packages/react-devtools-shared/src/__tests__/__snapshots__/profilingCommitTreeBuilder-test.js.snap
index 1fa419013d98f..43f3f91c7a24f 100644
--- a/packages/react-devtools-shared/src/__tests__/__snapshots__/profilingCommitTreeBuilder-test.js.snap
+++ b/packages/react-devtools-shared/src/__tests__/__snapshots__/profilingCommitTreeBuilder-test.js.snap
@@ -71,7 +71,7 @@ Object {
},
3 => Object {
"children": Array [
- 4,
+ 6,
],
"displayName": "Suspense",
"hocDisplayNames": null,
@@ -81,11 +81,11 @@ Object {
"treeBaseDuration": 0,
"type": 12,
},
- 4 => Object {
+ 6 => Object {
"children": Array [],
"displayName": "LazyInnerComponent",
"hocDisplayNames": null,
- "id": 4,
+ "id": 6,
"key": null,
"parentID": 3,
"treeBaseDuration": 0,
@@ -167,7 +167,7 @@ Object {
},
3 => Object {
"children": Array [
- 4,
+ 6,
],
"displayName": "Suspense",
"hocDisplayNames": null,
@@ -177,11 +177,11 @@ Object {
"treeBaseDuration": 0,
"type": 12,
},
- 4 => Object {
+ 6 => Object {
"children": Array [],
"displayName": "LazyInnerComponent",
"hocDisplayNames": null,
- "id": 4,
+ "id": 6,
"key": null,
"parentID": 3,
"treeBaseDuration": 0,
diff --git a/packages/react-devtools-shared/src/backend/renderer.js b/packages/react-devtools-shared/src/backend/renderer.js
index 3afbbf3dd4435..0ef641e410216 100644
--- a/packages/react-devtools-shared/src/backend/renderer.js
+++ b/packages/react-devtools-shared/src/backend/renderer.js
@@ -631,12 +631,16 @@ export function attach(
// Note that by calling these functions we may be creating the ID for the first time.
// If the Fiber is then never mounted, we are responsible for cleaning up after ourselves.
- // This is important because getPrimaryFiber() stores a Fiber in the primaryFibers Set.
- // If a Fiber never mounts, and we don't clean up after this code, we could leak.
+ // This is important because getOrGenerateFiberID() stores a Fiber in a couple of local Maps.
+ // If the Fiber never mounts and we don't clean up after this code, we could leak.
// Fortunately we would only leak Fibers that have errors/warnings associated with them,
// which is hopefully only a small set and only in DEV mode– but this is still not great.
// We should clean up Fibers like this when flushing; see recordPendingErrorsAndWarnings().
- const fiberID = getFiberID(getPrimaryFiber(fiber));
+ const fiberID = getOrGenerateFiberID(fiber);
+
+ if (__DEBUG__) {
+ debug('onErrorOrWarning', fiber, null, `${type}: "${message}"`);
+ }
// Mark this Fiber as needed its warning/error count updated during the next flush.
fibersWithChangedErrorOrWarningCounts.add(fiberID);
@@ -702,19 +706,20 @@ export function attach(
if (__DEBUG__) {
const displayName =
fiber.tag + ':' + (getDisplayNameForFiber(fiber) || 'null');
- const id = getFiberID(fiber);
+
+ const maybeID = getFiberIDUnsafe(fiber) || '';
const parentDisplayName = parentFiber
? parentFiber.tag +
':' +
(getDisplayNameForFiber(parentFiber) || 'null')
: '';
- const parentID = parentFiber ? getFiberID(parentFiber) : '';
- // NOTE: calling getFiberID or getPrimaryFiber is unsafe here
- // because it will put them in the map. For now, we'll omit them.
- // TODO: better debugging story for this.
+ const maybeParentID = parentFiber
+ ? getFiberIDUnsafe(parentFiber) || ''
+ : '';
+
console.log(
- `[renderer] %c${name} %c${displayName} (${id}) %c${
- parentFiber ? `${parentDisplayName} (${parentID})` : ''
+ `[renderer] %c${name} %c${displayName} (${maybeID}) %c${
+ parentFiber ? `${parentDisplayName} (${maybeParentID})` : ''
} %c${extraString}`,
'color: red; font-weight: bold;',
'color: blue;',
@@ -797,9 +802,15 @@ export function attach(
throw Error('Cannot modify filter preferences while profiling');
}
+ unmountAndRemountAllRoots(() => {
+ applyComponentFilters(componentFilters);
+ });
+ }
+
+ function unmountAndRemountAllRoots(callback?: Function) {
// Recursively unmount all roots.
hook.getFiberRoots(rendererID).forEach(root => {
- currentRootID = getFiberID(getPrimaryFiber(root.current));
+ currentRootID = getOrGenerateFiberID(root.current);
// The TREE_OPERATION_REMOVE_ROOT operation serves two purposes:
// 1. It avoids sending unnecessary bridge traffic to clear a root.
// 2. It preserves Fiber IDs when remounting (below) which in turn ID to error/warning mapping.
@@ -808,14 +819,16 @@ export function attach(
currentRootID = -1;
});
- applyComponentFilters(componentFilters);
+ if (typeof callback === 'function') {
+ callback();
+ }
// Reset pseudo counters so that new path selections will be persisted.
rootDisplayNameCounter.clear();
// Recursively re-mount all roots with new filter criteria applied.
hook.getFiberRoots(rendererID).forEach(root => {
- currentRootID = getFiberID(getPrimaryFiber(root.current));
+ currentRootID = getOrGenerateFiberID(root.current);
setRootPseudoKey(currentRootID, root.current);
mountFiberRecursively(root.current, null, false, false);
flushPendingEvents(root);
@@ -946,25 +959,16 @@ export function attach(
}
}
- // This is a slightly annoying indirection.
- // It is currently necessary because DevTools wants to use unique objects as keys for instances.
- // However fibers have two versions.
- // We use this set to remember first encountered fiber for each conceptual instance.
- function getPrimaryFiber(fiber: Fiber): Fiber {
- if (primaryFibers.has(fiber)) {
- return fiber;
- }
- const {alternate} = fiber;
- if (alternate != null && primaryFibers.has(alternate)) {
- return alternate;
- }
- primaryFibers.add(fiber);
- return fiber;
- }
-
+ // Map of one or more Fibers in a pair to their unique id number.
+ // We track both Fibers to support Fast Refresh,
+ // which may forcefully replace one of the pair as part of hot reloading.
+ // In that case it's still important to be able to locate the previous ID during subsequent renders.
const fiberToIDMap: Map = new Map();
- const idToFiberMap: Map = new Map();
- const primaryFibers: Set = new Set();
+
+ // Map of id to one (arbitrary) Fiber in a pair.
+ // This Map is used to e.g. get the display name for a Fiber or schedule an update,
+ // operations that should be the same whether the current and work-in-progress Fiber is used.
+ const idToArbitraryFiberMap: Map = new Map();
// When profiling is supported, we store the latest tree base durations for each Fiber.
// This is so that we can quickly capture a snapshot of those values if profiling starts.
@@ -979,13 +983,84 @@ export function attach(
// When a mount or update is in progress, this value tracks the root that is being operated on.
let currentRootID: number = -1;
- function getFiberID(primaryFiber: Fiber): number {
- if (!fiberToIDMap.has(primaryFiber)) {
- const id = getUID();
- fiberToIDMap.set(primaryFiber, id);
- idToFiberMap.set(id, primaryFiber);
+ // Returns the unique ID for a Fiber or generates and caches a new one if the Fiber hasn't been seen before.
+ // Once this method has been called for a Fiber, untrackFiberID() should always be called later to avoid leaking.
+ function getOrGenerateFiberID(fiber: Fiber): number {
+ let id = null;
+ if (fiberToIDMap.has(fiber)) {
+ id = fiberToIDMap.get(fiber);
+ } else {
+ const {alternate} = fiber;
+ if (alternate !== null && fiberToIDMap.has(alternate)) {
+ id = fiberToIDMap.get(alternate);
+ }
+ }
+
+ if (id === null) {
+ id = getUID();
+ }
+
+ // This refinement is for Flow purposes only.
+ const refinedID = ((id: any): number);
+
+ // Make sure we're tracking this Fiber
+ // e.g. if it just mounted or an error was logged during initial render.
+ if (!fiberToIDMap.has(fiber)) {
+ fiberToIDMap.set(fiber, refinedID);
+ idToArbitraryFiberMap.set(refinedID, fiber);
+ }
+
+ // Also make sure we're tracking its alternate,
+ // e.g. in case this is the first update after mount.
+ const {alternate} = fiber;
+ if (alternate !== null) {
+ if (!fiberToIDMap.has(alternate)) {
+ fiberToIDMap.set(alternate, refinedID);
+ }
+ }
+
+ return refinedID;
+ }
+
+ // Returns an ID if one has already been generated for the Fiber or throws.
+ function getFiberIDThrows(fiber: Fiber): number {
+ const maybeID = getFiberIDUnsafe(fiber);
+ if (maybeID !== null) {
+ return maybeID;
+ }
+ throw Error(
+ `Could not find ID for Fiber "${getDisplayNameForFiber(fiber) || ''}"`,
+ );
+ }
+
+ // Returns an ID if one has already been generated for the Fiber or null if one has not been generated.
+ // Use this method while e.g. logging to avoid over-retaining Fibers.
+ function getFiberIDUnsafe(fiber: Fiber): number | null {
+ if (fiberToIDMap.has(fiber)) {
+ return ((fiberToIDMap.get(fiber): any): number);
+ } else {
+ const {alternate} = fiber;
+ if (alternate !== null && fiberToIDMap.has(alternate)) {
+ return ((fiberToIDMap.get(alternate): any): number);
+ }
+ }
+ return null;
+ }
+
+ // Removes a Fiber (and its alternate) from the Maps used to track their id.
+ // This method should always be called when a Fiber is unmounting.
+ function untrackFiberID(fiber: Fiber) {
+ const fiberID = getFiberIDUnsafe(fiber);
+ if (fiberID !== null) {
+ idToArbitraryFiberMap.delete(fiberID);
+ }
+
+ fiberToIDMap.delete(fiber);
+
+ const {alternate} = fiber;
+ if (alternate !== null) {
+ fiberToIDMap.delete(alternate);
}
- return ((fiberToIDMap.get(primaryFiber): any): number);
}
function getChangeDescription(
@@ -1046,7 +1121,7 @@ export function attach(
switch (getElementTypeForFiber(fiber)) {
case ElementTypeClass:
if (idToContextsMap !== null) {
- const id = getFiberID(getPrimaryFiber(fiber));
+ const id = getFiberIDThrows(fiber);
const contexts = getContextsForFiber(fiber);
if (contexts !== null) {
idToContextsMap.set(id, contexts);
@@ -1102,7 +1177,7 @@ export function attach(
switch (getElementTypeForFiber(fiber)) {
case ElementTypeClass:
if (idToContextsMap !== null) {
- const id = getFiberID(getPrimaryFiber(fiber));
+ const id = getFiberIDThrows(fiber);
const prevContexts = idToContextsMap.has(id)
? idToContextsMap.get(id)
: null;
@@ -1370,15 +1445,13 @@ export function attach(
clearPendingErrorsAndWarningsAfterDelay();
fibersWithChangedErrorOrWarningCounts.forEach(fiberID => {
- const fiber = idToFiberMap.get(fiberID);
+ const fiber = idToArbitraryFiberMap.get(fiberID);
if (fiber != null) {
// Don't send updates for Fibers that didn't mount due to e.g. Suspense or an error boundary.
// We may also need to clean up after ourselves to avoid leaks.
// See inline comments in onErrorOrWarning() for more info.
if (isFiberMountedImpl(fiber) !== MOUNTED) {
- fiberToIDMap.delete(fiber);
- idToFiberMap.delete(fiberID);
- primaryFibers.delete(fiber);
+ untrackFiberID(fiber);
return;
}
@@ -1539,7 +1612,7 @@ export function attach(
}
const isRoot = fiber.tag === HostRoot;
- const id = getFiberID(getPrimaryFiber(fiber));
+ const id = getOrGenerateFiberID(fiber);
const hasOwnerMetadata = fiber.hasOwnProperty('_debugOwner');
const isProfilingSupported = fiber.hasOwnProperty('treeBaseDuration');
@@ -1562,11 +1635,8 @@ export function attach(
const elementType = getElementTypeForFiber(fiber);
const {_debugOwner} = fiber;
- const ownerID =
- _debugOwner != null ? getFiberID(getPrimaryFiber(_debugOwner)) : 0;
- const parentID = parentFiber
- ? getFiberID(getPrimaryFiber(parentFiber))
- : 0;
+ const ownerID = _debugOwner != null ? getFiberIDThrows(_debugOwner) : 0;
+ const parentID = parentFiber ? getFiberIDThrows(parentFiber) : 0;
const displayNameStringID = getStringID(displayName);
@@ -1601,6 +1671,20 @@ export function attach(
);
}
+ const unsafeID = getFiberIDUnsafe(fiber);
+ if (fiber._debugNeedsRemount) {
+ if (unsafeID === null) {
+ // This inidicates a case we can't recover from:
+ // Fast Refresh has force remounted a component in a way that we don't have an id for.
+ // We could throw but that's a bad user experience.
+ // Or we could ignore the unmount but then Store might end up with a duplicate node.
+ // So a fallback is to completely reset the Store.
+ // This is costly but since Fast Refresh is only used in DEV builds, it should be okay.
+ setTimeout(unmountAndRemountAllRoots, 0);
+ return;
+ }
+ }
+
if (trackedPathMatchFiber !== null) {
// We're in the process of trying to restore previous selection.
// If this fiber matched but is being unmounted, there's no use trying.
@@ -1613,20 +1697,18 @@ export function attach(
}
}
- const isRoot = fiber.tag === HostRoot;
- const primaryFiber = getPrimaryFiber(fiber);
- if (!fiberToIDMap.has(primaryFiber)) {
+ if (unsafeID === null) {
// If we've never seen this Fiber, it might be inside of a legacy render Suspense fragment (so the store is not even aware of it).
// In that case we can just ignore it or it will cause errors later on.
// One example of this is a Lazy component that never resolves before being unmounted.
//
// TODO: This is fragile and can obscure actual bugs.
- //
- // Calling getPrimaryFiber() lazily adds fibers to the Map, so clean up after ourselves before returning.
- primaryFibers.delete(primaryFiber);
return;
}
- const id = getFiberID(primaryFiber);
+
+ // Flow refinement.
+ const id = ((unsafeID: any): number);
+ const isRoot = fiber.tag === HostRoot;
if (isRoot) {
// Roots must be removed only after all children (pending and simulated) have been removed.
// So we track it separately.
@@ -1641,14 +1723,15 @@ export function attach(
pendingRealUnmountedIDs.push(id);
}
}
- fiberToIDMap.delete(primaryFiber);
- idToFiberMap.delete(id);
- primaryFibers.delete(primaryFiber);
- const isProfilingSupported = fiber.hasOwnProperty('treeBaseDuration');
- if (isProfilingSupported) {
- idToRootMap.delete(id);
- idToTreeBaseDurationMap.delete(id);
+ if (!fiber._debugNeedsRemount) {
+ untrackFiberID(fiber);
+
+ const isProfilingSupported = fiber.hasOwnProperty('treeBaseDuration');
+ if (isProfilingSupported) {
+ idToRootMap.delete(id);
+ idToTreeBaseDurationMap.delete(id);
+ }
}
}
@@ -1675,6 +1758,9 @@ export function attach(
const shouldIncludeInTree = !shouldFilterFiber(fiber);
if (shouldIncludeInTree) {
recordMount(fiber, parentFiber);
+ } else {
+ // Generate an ID even for filtered Fibers, in case it's needed later (e.g. for Profiling).
+ getOrGenerateFiberID(fiber);
}
if (traceUpdatesEnabled) {
@@ -1785,7 +1871,7 @@ export function attach(
}
function recordProfilingDurations(fiber: Fiber) {
- const id = getFiberID(getPrimaryFiber(fiber));
+ const id = getFiberIDThrows(fiber);
const {actualDuration, treeBaseDuration} = fiber;
idToTreeBaseDurationMap.set(id, treeBaseDuration || 0);
@@ -1873,7 +1959,7 @@ export function attach(
return;
}
pushOperation(TREE_OPERATION_REORDER_CHILDREN);
- pushOperation(getFiberID(getPrimaryFiber(fiber)));
+ pushOperation(getFiberIDThrows(fiber));
pushOperation(numChildren);
for (let i = 0; i < nextChildren.length; i++) {
pushOperation(nextChildren[i]);
@@ -1885,7 +1971,7 @@ export function attach(
nextChildren: Array,
) {
if (!shouldFilterFiber(fiber)) {
- nextChildren.push(getFiberID(getPrimaryFiber(fiber)));
+ nextChildren.push(getFiberIDThrows(fiber));
} else {
let child = fiber.child;
const isTimedOutSuspense =
@@ -1923,6 +2009,8 @@ export function attach(
debug('updateFiberRecursively()', nextFiber, parentFiber);
}
+ const id = getOrGenerateFiberID(nextFiber);
+
if (traceUpdatesEnabled) {
const elementType = getElementTypeForFiber(nextFiber);
if (traceNearestHostComponentUpdate) {
@@ -1948,8 +2036,7 @@ export function attach(
if (
mostRecentlyInspectedElement !== null &&
- mostRecentlyInspectedElement.id ===
- getFiberID(getPrimaryFiber(nextFiber)) &&
+ mostRecentlyInspectedElement.id === id &&
didFiberRender(prevFiber, nextFiber)
) {
// If this Fiber has updated, clear cached inspected data.
@@ -2093,7 +2180,7 @@ export function attach(
// we should fall back to recursively marking the nearest host descendants for highlight.
if (traceNearestHostComponentUpdate) {
const hostFibers = findAllCurrentHostFibers(
- getFiberID(getPrimaryFiber(nextFiber)),
+ getFiberIDThrows(nextFiber),
);
hostFibers.forEach(hostFiber => {
traceUpdatesForNodes.add(hostFiber.stateNode);
@@ -2177,7 +2264,7 @@ export function attach(
}
// If we have not been profiling, then we can just walk the tree and build up its current state as-is.
hook.getFiberRoots(rendererID).forEach(root => {
- currentRootID = getFiberID(getPrimaryFiber(root.current));
+ currentRootID = getOrGenerateFiberID(root.current);
setRootPseudoKey(currentRootID, root.current);
// Handle multi-renderer edge-case where only some v16 renderers support profiling.
@@ -2232,7 +2319,7 @@ export function attach(
const current = root.current;
const alternate = current.alternate;
- currentRootID = getFiberID(getPrimaryFiber(current));
+ currentRootID = getOrGenerateFiberID(current);
// Before the traversals, remember to start tracking
// our path in case we have selection to restore.
@@ -2378,7 +2465,7 @@ export function attach(
}
function getDisplayNameForFiberID(id) {
- const fiber = idToFiberMap.get(id);
+ const fiber = idToArbitraryFiberMap.get(id);
return fiber != null ? getDisplayNameForFiber(((fiber: any): Fiber)) : null;
}
@@ -2393,7 +2480,7 @@ export function attach(
fiber = fiber.return;
}
}
- return getFiberID(getPrimaryFiber(((fiber: any): Fiber)));
+ return getFiberIDThrows(((fiber: any): Fiber));
}
return null;
}
@@ -2463,7 +2550,7 @@ export function attach(
// It would be nice if we updated React to inject this function directly (vs just indirectly via findDOMNode).
// BEGIN copied code
function findCurrentFiberUsingSlowPathById(id: number): Fiber | null {
- const fiber = idToFiberMap.get(id);
+ const fiber = idToArbitraryFiberMap.get(id);
if (fiber == null) {
console.warn(`Could not find Fiber with id "${id}"`);
return null;
@@ -2625,7 +2712,7 @@ export function attach(
}
function prepareViewElementSource(id: number): void {
- const fiber = idToFiberMap.get(id);
+ const fiber = idToArbitraryFiberMap.get(id);
if (fiber == null) {
console.warn(`Could not find Fiber with id "${id}"`);
return;
@@ -2659,7 +2746,7 @@ export function attach(
function fiberToSerializedElement(fiber: Fiber): SerializedElement {
return {
displayName: getDisplayNameForFiber(fiber) || 'Anonymous',
- id: getFiberID(getPrimaryFiber(fiber)),
+ id: getFiberIDThrows(fiber),
key: fiber.key,
type: getElementTypeForFiber(fiber),
};
@@ -2990,7 +3077,7 @@ export function attach(
function updateSelectedElement(inspectedElement: InspectedElement): void {
const {hooks, id, props} = inspectedElement;
- const fiber = idToFiberMap.get(id);
+ const fiber = idToArbitraryFiberMap.get(id);
if (fiber == null) {
console.warn(`Could not find Fiber with id "${id}"`);
return;
@@ -3508,7 +3595,7 @@ export function attach(
idToContextsMap = new Map();
hook.getFiberRoots(rendererID).forEach(root => {
- const rootID = getFiberID(getPrimaryFiber(root.current));
+ const rootID = getFiberIDThrows(root.current);
((displayNamesByRootID: any): DisplayNamesByRootID).set(
rootID,
getDisplayNameForRoot(root.current),
@@ -3551,8 +3638,8 @@ export function attach(
const forceFallbackForSuspenseIDs = new Set();
function shouldSuspendFiberAccordingToSet(fiber) {
- const id = getFiberID(getPrimaryFiber(((fiber: any): Fiber)));
- return forceFallbackForSuspenseIDs.has(id);
+ const maybeID = getFiberIDUnsafe(((fiber: any): Fiber));
+ return maybeID !== null && forceFallbackForSuspenseIDs.has(maybeID);
}
function overrideSuspense(id, forceFallback) {
@@ -3577,7 +3664,7 @@ export function attach(
setSuspenseHandler(shouldSuspendFiberAlwaysFalse);
}
}
- const fiber = idToFiberMap.get(id);
+ const fiber = idToArbitraryFiberMap.get(id);
if (fiber != null) {
scheduleUpdate(fiber);
}
@@ -3728,7 +3815,7 @@ export function attach(
case HostRoot:
// Roots don't have a real displayName, index, or key.
// Instead, we'll use the pseudo key (childDisplayName:indexWithThatName).
- const id = getFiberID(getPrimaryFiber(fiber));
+ const id = getFiberIDThrows(fiber);
const pseudoKey = rootPseudoKeys.get(id);
if (pseudoKey === undefined) {
throw new Error('Expected mounted root to have known pseudo key.');
@@ -3753,7 +3840,7 @@ export function attach(
// The return path will contain Fibers that are "invisible" to the store
// because their keys and indexes are important to restoring the selection.
function getPathForElement(id: number): Array | null {
- let fiber = idToFiberMap.get(id);
+ let fiber = idToArbitraryFiberMap.get(id);
if (fiber == null) {
return null;
}
@@ -3784,7 +3871,7 @@ export function attach(
return null;
}
return {
- id: getFiberID(getPrimaryFiber(fiber)),
+ id: getFiberIDThrows(fiber),
isFullMatch: trackedPathMatchDepth === trackedPath.length - 1,
};
}