[Fresh] Implement missing features (#15860)
* Fix existing test This test included a change in variable name. It wasn't needed, and distracts from the actual thing being tested (the annotation). * Reset state on edits to initial state argument * Add a way to check whether there are hot updates prepareUpdate() now returns null if there are none. * Add a way to query host nodes for families
Dan Abramov committed
Jun 11, 2019 at 19:37 UTC
191920605fa88949e640dbf6da4f8659020a4566
7 files changed
+407
-37
packages/react-fresh/src/ReactFreshBabelPlugin.js
+11
@@ -175,6 +175,17 @@ export default function(babel) {
175
// TODO: if there is no LHS, consider some other heuristic.
176
key = hookCallPath.parentPath.get('id').getSource();
177
}
178
+
179
+ // Some built-in Hooks reset on edits to arguments.
180
+ const args = hookCallPath.get('arguments');
181
+ if (hookName === 'useState' && args.length > 0) {
182
+ // useState second argument is initial state.
183
+ key += '(' + args[0].getSource() + ')';
184
+ } else if (hookName === 'useReducer' && args.length > 1) {
185
+ // useReducer second argument is initial state.
186
+ key += '(' + args[1].getSource() + ')';
187
+ }
188
+
189
hookCallsForFn.push({
190
name: hookName,
191
callee: hookCallPath.node.callee,
packages/react-fresh/src/ReactFreshRuntime.js
+9
-1
@@ -115,7 +115,11 @@ function resolveFamily(type) {
115
return familiesByType.get(type);
116
}
117
118
-export function prepareUpdate(): HotUpdate {
118
+export function prepareUpdate(): HotUpdate | null {
119
+ if (pendingUpdates.length === 0) {
120
+ return null;
121
+ }
122
+
123
const staleFamilies = new Set();
124
const updatedFamilies = new Set();
125
@@ -206,3 +210,7 @@ export function collectCustomHooksForSignature(type: any) {
210
computeFullKey(signature);
211
}
212
}
213
+
214
+export function getFamilyByID(id: string): Family | void {
215
+ return allFamiliesByID.get(id);
216
+}
packages/react-fresh/src/__tests__/ReactFresh-test.js
+110
@@ -20,6 +20,7 @@ let act;
20
describe('ReactFresh', () => {
21
let container;
22
let lastRoot;
23
+ let findHostNodesForHotUpdate;
24
let scheduleHotUpdate;
25
26
beforeEach(() => {
@@ -27,6 +28,7 @@ describe('ReactFresh', () => {
28
supportsFiber: true,
29
inject: injected => {
30
scheduleHotUpdate = injected.scheduleHotUpdate;
31
+ findHostNodesForHotUpdate = injected.findHostNodesForHotUpdate;
32
},
33
onCommitFiberRoot: (id, root) => {
34
lastRoot = root;
@@ -2934,4 +2936,112 @@ describe('ReactFresh', () => {
2936
expect(finalEl.textContent).toBe('1');
2937
}
2938
});
2939
+
2940
+ it('can find host nodes for a family', () => {
2941
+ if (__DEV__) {
2942
+ render(() => {
2943
+ function Child({children}) {
2944
+ return <div className="Child">{children}</div>;
2945
+ }
2946
+ __register__(Child, 'Child');
2947
+
2948
+ function Parent({children}) {
2949
+ return (
2950
+ <div className="Parent">
2951
+ <div>
2952
+ <Child />
2953
+ </div>
2954
+ <div>
2955
+ <Child />
2956
+ </div>
2957
+ </div>
2958
+ );
2959
+ }
2960
+ __register__(Parent, 'Parent');
2961
+
2962
+ function App() {
2963
+ return (
2964
+ <div className="App">
2965
+ <Parent />
2966
+ <Cls>
2967
+ <Parent />
2968
+ </Cls>
2969
+ <Indirection>
2970
+ <Empty />
2971
+ </Indirection>
2972
+ </div>
2973
+ );
2974
+ }
2975
+ __register__(App, 'App');
2976
+
2977
+ class Cls extends React.Component {
2978
+ render() {
2979
+ return this.props.children;
2980
+ }
2981
+ }
2982
+
2983
+ function Indirection({children}) {
2984
+ return children;
2985
+ }
2986
+
2987
+ function Empty() {
2988
+ return null;
2989
+ }
2990
+ __register__(Empty, 'Empty');
2991
+
2992
+ function Frag() {
2993
+ return (
2994
+ <React.Fragment>
2995
+ <div className="Frag">
2996
+ <div />
2997
+ </div>
2998
+ <div className="Frag">
2999
+ <div />
3000
+ </div>
3001
+ </React.Fragment>
3002
+ );
3003
+ }
3004
+ __register__(Frag, 'Frag');
3005
+
3006
+ return App;
3007
+ });
3008
+
3009
+ const parentFamily = ReactFreshRuntime.getFamilyByID('Parent');
3010
+ const childFamily = ReactFreshRuntime.getFamilyByID('Child');
3011
+ const emptyFamily = ReactFreshRuntime.getFamilyByID('Empty');
3012
+
3013
+ testFindNodesForFamilies(
3014
+ [parentFamily],
3015
+ container.querySelectorAll('.Parent'),
3016
+ );
3017
+
3018
+ testFindNodesForFamilies(
3019
+ [childFamily],
3020
+ container.querySelectorAll('.Child'),
3021
+ );
3022
+
3023
+ // When searching for both Parent and Child,
3024
+ // we'll stop visual highlighting at the Parent.
3025
+ testFindNodesForFamilies(
3026
+ [parentFamily, childFamily],
3027
+ container.querySelectorAll('.Parent'),
3028
+ );
3029
+
3030
+ // When we can't find host nodes, use the closest parent.
3031
+ testFindNodesForFamilies(
3032
+ [emptyFamily],
3033
+ container.querySelectorAll('.App'),
3034
+ );
3035
+ }
3036
+ });
3037
+
3038
+ function testFindNodesForFamilies(families, expectedNodes) {
3039
+ const foundNodes = Array.from(
3040
+ findHostNodesForHotUpdate(lastRoot, families),
3041
+ );
3042
+ expect(foundNodes.length).toEqual(expectedNodes.length);
3043
+ foundNodes.forEach((node, i) => {
3044
+ expect(node).toBe(expectedNodes[i]);
3045
+ });
3046
+ }
3047
});
packages/react-fresh/src/__tests__/ReactFreshIntegration-test.js
+131
-29
@@ -73,6 +73,8 @@ describe('ReactFreshIntegration', () => {
73
act(() => {
74
ReactDOM.render(<Component />, container);
75
});
76
+ // Module initialization shouldn't be counted as a hot update.
77
+ expect(ReactFreshRuntime.prepareUpdate()).toBe(null);
78
}
79
80
function patch(source) {
@@ -275,9 +277,10 @@ describe('ReactFreshIntegration', () => {
277
if (__DEV__) {
278
render(`
279
const {useState} = React;
280
+ const S = 1;
281
282
export default function App() {
280
- const [foo, setFoo] = useState(1);
283
+ const [foo, setFoo] = useState(S);
284
return <h1>A{foo}</h1>;
285
}
286
`);
@@ -286,9 +289,10 @@ describe('ReactFreshIntegration', () => {
289
290
patch(`
291
const {useState} = React;
292
+ const S = 2;
293
294
export default function App() {
291
- const [foo, setFoo] = useState('ignored');
295
+ const [foo, setFoo] = useState(S);
296
return <h1>B{foo}</h1>;
297
}
298
`);
@@ -298,16 +302,17 @@ describe('ReactFreshIntegration', () => {
302
303
patch(`
304
const {useState} = React;
305
+ const S = 3;
306
307
export default function App() {
303
- const [bar, setBar] = useState(2);
308
+ const [bar, setBar] = useState(S);
309
return <h1>C{bar}</h1>;
310
}
311
`);
312
// Different state variable name, so state is reset.
313
expect(container.firstChild).not.toBe(el);
314
const newEl = container.firstChild;
310
- expect(newEl.textContent).toBe('C2');
315
+ expect(newEl.textContent).toBe('C3');
316
}
317
});
318
@@ -315,10 +320,11 @@ describe('ReactFreshIntegration', () => {
320
if (__DEV__) {
321
render(`
322
const {useState} = React;
323
+ const S = 1;
324
325
function hoc(Wrapped) {
326
return function Generated() {
321
- const [foo, setFoo] = useState(1);
327
+ const [foo, setFoo] = useState(S);
328
return <Wrapped value={foo} />;
329
};
330
}
@@ -332,10 +338,11 @@ describe('ReactFreshIntegration', () => {
338
339
patch(`
340
const {useState} = React;
341
+ const S = 2;
342
343
function hoc(Wrapped) {
344
return function Generated() {
338
- const [foo, setFoo] = useState('ignored');
345
+ const [foo, setFoo] = useState(S);
346
return <Wrapped value={foo} />;
347
};
348
}
@@ -350,10 +357,11 @@ describe('ReactFreshIntegration', () => {
357
358
patch(`
359
const {useState} = React;
360
+ const S = 3;
361
362
function hoc(Wrapped) {
363
return function Generated() {
356
- const [bar, setBar] = useState(2);
364
+ const [bar, setBar] = useState(S);
365
return <Wrapped value={bar} />;
366
};
367
}
@@ -365,7 +373,7 @@ describe('ReactFreshIntegration', () => {
373
// Different state variable name, so state is reset.
374
expect(container.firstChild).not.toBe(el);
375
const newEl = container.firstChild;
368
- expect(newEl.textContent).toBe('C2');
376
+ expect(newEl.textContent).toBe('C3');
377
}
378
});
379
@@ -373,10 +381,11 @@ describe('ReactFreshIntegration', () => {
381
if (__DEV__) {
382
render(`
383
const {useState} = React;
384
+ const S = 1;
385
386
function hoc(Wrapped) {
387
return function Generated() {
379
- const [foo, setFoo] = useState(1);
388
+ const [foo, setFoo] = useState(S);
389
return <Wrapped value={foo} />;
390
};
391
}
@@ -392,10 +401,11 @@ describe('ReactFreshIntegration', () => {
401
402
patch(`
403
const {useState} = React;
404
+ const S = 2;
405
406
function hoc(Wrapped) {
407
return function Generated() {
398
- const [foo, setFoo] = useState('ignored');
408
+ const [foo, setFoo] = useState(S);
409
return <Wrapped value={foo} />;
410
};
411
}
@@ -412,10 +422,11 @@ describe('ReactFreshIntegration', () => {
422
423
patch(`
424
const {useState} = React;
425
+ const S = 3;
426
427
function hoc(Wrapped) {
428
return function Generated() {
418
- const [bar, setBar] = useState(2);
429
+ const [bar, setBar] = useState(S);
430
return <Wrapped value={bar} />;
431
};
432
}
@@ -429,7 +440,7 @@ describe('ReactFreshIntegration', () => {
440
// Different state variable name, so state is reset.
441
expect(container.firstChild).not.toBe(el);
442
const newEl = container.firstChild;
432
- expect(newEl.textContent).toBe('C2');
443
+ expect(newEl.textContent).toBe('C3');
444
}
445
});
446
@@ -757,9 +768,10 @@ describe('ReactFreshIntegration', () => {
768
if (__DEV__) {
769
render(`
770
const {useState} = React;
771
+ const S = 1;
772
773
export default function App() {
762
- const [foo, setFoo] = useState(1);
774
+ const [foo, setFoo] = useState(S);
775
return <h1>A{foo}</h1>;
776
}
777
`);
@@ -768,9 +780,10 @@ describe('ReactFreshIntegration', () => {
780
781
patch(`
782
const {useState} = React;
783
+ const S = 2;
784
785
export default function App() {
773
- const [foo, setFoo] = useState('ignored');
786
+ const [foo, setFoo] = useState(S);
787
return <h1>B{foo}</h1>;
788
}
789
`);
@@ -780,75 +793,164 @@ describe('ReactFreshIntegration', () => {
793
794
patch(`
795
const {useState} = React;
796
+ const S = 3;
797
798
/* @hot reset */
799
800
export default function App() {
787
- const [bar, setBar] = useState(2);
788
- return <h1>C{bar}</h1>;
801
+ const [foo, setFoo] = useState(S);
802
+ return <h1>C{foo}</h1>;
803
}
804
`);
805
// Found remount annotation, so state is reset.
806
expect(container.firstChild).not.toBe(el);
807
el = container.firstChild;
794
- expect(el.textContent).toBe('C2');
808
+ expect(el.textContent).toBe('C3');
809
810
patch(`
811
const {useState} = React;
812
+ const S = 4;
813
814
export default function App() {
815
816
// @hot reset
817
803
- const [bar, setBar] = useState(3);
804
- return <h1>D{bar}</h1>;
818
+ const [foo, setFoo] = useState(S);
819
+ return <h1>D{foo}</h1>;
820
}
821
`);
822
// Found remount annotation, so state is reset.
823
expect(container.firstChild).not.toBe(el);
824
el = container.firstChild;
810
- expect(el.textContent).toBe('D3');
825
+ expect(el.textContent).toBe('D4');
826
827
patch(`
828
const {useState} = React;
829
+ const S = 5;
830
831
export default function App() {
816
- const [bar, setBar] = useState(4);
817
- return <h1>E{bar}</h1>;
832
+ const [foo, setFoo] = useState(S);
833
+ return <h1>E{foo}</h1>;
834
}
835
`);
836
// There is no remount annotation anymore,
837
// so preserve the previous state.
838
expect(container.firstChild).toBe(el);
823
- expect(el.textContent).toBe('E3');
839
+ expect(el.textContent).toBe('E4');
840
841
patch(`
842
const {useState} = React;
843
+ const S = 6;
844
845
export default function App() {
829
- const [bar, setBar] = useState(4);
830
- return <h1>F{bar}</h1>;
846
+ const [foo, setFoo] = useState(S);
847
+ return <h1>F{foo}</h1>;
848
}
849
`);
850
// Continue editing.
851
expect(container.firstChild).toBe(el);
835
- expect(el.textContent).toBe('F3');
852
+ expect(el.textContent).toBe('F4');
853
854
patch(`
855
const {useState} = React;
856
+ const S = 7;
857
858
export default function App() {
859
860
/* @hot reset */
861
844
- const [bar, setBar] = useState(5);
845
- return <h1>G{bar}</h1>;
862
+ const [foo, setFoo] = useState(S);
863
+ return <h1>G{foo}</h1>;
864
}
865
`);
866
// Force remount one last time.
867
expect(container.firstChild).not.toBe(el);
868
el = container.firstChild;
851
- expect(el.textContent).toBe('G5');
869
+ expect(el.textContent).toBe('G7');
870
+ }
871
+ });
872
+
873
+ // This is best effort for simple cases.
874
+ // We won't attempt to resolve identifiers.
875
+ it('resets state when useState initial state is edited', () => {
876
+ if (__DEV__) {
877
+ render(`
878
+ const {useState} = React;
879
+
880
+ export default function App() {
881
+ const [foo, setFoo] = useState(1);
882
+ return <h1>A{foo}</h1>;
883
+ }
884
+ `);
885
+ let el = container.firstChild;
886
+ expect(el.textContent).toBe('A1');
887
+
888
+ patch(`
889
+ const {useState} = React;
890
+
891
+ export default function App() {
892
+ const [foo, setFoo] = useState(1);
893
+ return <h1>B{foo}</h1>;
894
+ }
895
+ `);
896
+ // Same initial state, so it's preserved.
897
+ expect(container.firstChild).toBe(el);
898
+ expect(el.textContent).toBe('B1');
899
+
900
+ patch(`
901
+ const {useState} = React;
902
+
903
+ export default function App() {
904
+ const [foo, setFoo] = useState(2);
905
+ return <h1>C{foo}</h1>;
906
+ }
907
+ `);
908
+ // Different initial state, so state is reset.
909
+ expect(container.firstChild).not.toBe(el);
910
+ el = container.firstChild;
911
+ expect(el.textContent).toBe('C2');
912
+ }
913
+ });
914
+
915
+ // This is best effort for simple cases.
916
+ // We won't attempt to resolve identifiers.
917
+ it('resets state when useReducer initial state is edited', () => {
918
+ if (__DEV__) {
919
+ render(`
920
+ const {useReducer} = React;
921
+
922
+ export default function App() {
923
+ const [foo, setFoo] = useReducer(x => x, 1);
924
+ return <h1>A{foo}</h1>;
925
+ }
926
+ `);
927
+ let el = container.firstChild;
928
+ expect(el.textContent).toBe('A1');
929
+
930
+ patch(`
931
+ const {useReducer} = React;
932
+
933
+ export default function App() {
934
+ const [foo, setFoo] = useReducer(x => x, 1);
935
+ return <h1>B{foo}</h1>;
936
+ }
937
+ `);
938
+ // Same initial state, so it's preserved.
939
+ expect(container.firstChild).toBe(el);
940
+ expect(el.textContent).toBe('B1');
941
+
942
+ patch(`
943
+ const {useReducer} = React;
944
+
945
+ export default function App() {
946
+ const [foo, setFoo] = useReducer(x => x, 2);
947
+ return <h1>C{foo}</h1>;
948
+ }
949
+ `);
950
+ // Different initial state, so state is reset.
951
+ expect(container.firstChild).not.toBe(el);
952
+ el = container.firstChild;
953
+ expect(el.textContent).toBe('C2');
954
}
955
});
956
packages/react-fresh/src/__tests__/__snapshots__/ReactFreshBabelPlugin-test.js.snap
+6
-6
@@ -11,7 +11,7 @@ export default function App() {
11
return <h1>{foo}</h1>;
12
}
13
14
-_s(App, "useState{[foo, setFoo]}\\nuseEffect{}");
14
+_s(App, "useState{[foo, setFoo](0)}\\nuseEffect{}");
15
16
_c = App;
17
@@ -30,7 +30,7 @@ export const A = _c3 = React.memo(_c2 = React.forwardRef(_c = _s((props, ref) =>
30
const [foo, setFoo] = useState(0);
31
React.useEffect(() => {});
32
return <h1 ref={ref}>{foo}</h1>;
33
-}, "useState{[foo, setFoo]}\\nuseEffect{}")));
33
+}, "useState{[foo, setFoo](0)}\\nuseEffect{}")));
34
35
export const B = _c6 = React.memo(_c5 = React.forwardRef(_c4 = _s2(function (props, ref) {
36
_s2();
@@ -38,7 +38,7 @@ export const B = _c6 = React.memo(_c5 = React.forwardRef(_c4 = _s2(function (pro
38
const [foo, setFoo] = useState(0);
39
React.useEffect(() => {});
40
return <h1 ref={ref}>{foo}</h1>;
41
-}, "useState{[foo, setFoo]}\\nuseEffect{}")));
41
+}, "useState{[foo, setFoo](0)}\\nuseEffect{}")));
42
43
function hoc() {
44
var _s3 = __signature__();
@@ -49,7 +49,7 @@ function hoc() {
49
const [foo, setFoo] = useState(0);
50
React.useEffect(() => {});
51
return <h1 ref={ref}>{foo}</h1>;
52
- }, "useState{[foo, setFoo]}\\nuseEffect{}");
52
+ }, "useState{[foo, setFoo](0)}\\nuseEffect{}");
53
}
54
55
export let C = hoc();
@@ -87,7 +87,7 @@ export default function App() {
87
return foo;
88
}
89
90
- _s(useFancyState, 'useState{[foo, setFoo]}\\nuseFancyEffect{}', true);
90
+ _s(useFancyState, 'useState{[foo, setFoo](0)}\\nuseFancyEffect{}', true);
91
92
const bar = useFancyState();
93
const baz = FancyHook.useThing();
@@ -169,7 +169,7 @@ function useFancyState() {
169
return foo;
170
}
171
172
-_s(useFancyState, "useState{[foo, setFoo]}\\nuseFancyEffect{}", false, () => [useFancyEffect]);
172
+_s(useFancyState, "useState{[foo, setFoo](0)}\\nuseFancyEffect{}", false, () => [useFancyEffect]);
173
174
const useFancyEffect = () => {
175
_s2();
packages/react-reconciler/src/ReactFiberHotReloading.js
+135
@@ -10,6 +10,7 @@
10
import type {ReactElement} from 'shared/ReactElementType';
11
import type {Fiber} from './ReactFiber';
12
import type {FiberRoot} from './ReactFiberRoot';
13
+import type {Instance} from './ReactFiberHostConfig';
14
15
import {
16
flushSync,
@@ -21,6 +22,9 @@ import {
22
ClassComponent,
23
FunctionComponent,
24
ForwardRef,
25
+ HostComponent,
26
+ HostPortal,
27
+ HostRoot,
28
MemoComponent,
29
SimpleMemoComponent,
30
} from 'shared/ReactWorkTags';
@@ -287,3 +291,134 @@ function scheduleFibersWithFamiliesRecursively(
291
}
292
}
293
}
294
+
295
+export function findHostNodesForHotUpdate(
296
+ root: FiberRoot,
297
+ families: Array<Family>,
298
+): Set<Instance> {
299
+ if (__DEV__) {
300
+ const hostNodes = new Set();
301
+ const types = new Set(families.map(family => family.current));
302
+ findHostNodesForMatchingFibersRecursively(root.current, types, hostNodes);
303
+ return hostNodes;
304
+ } else {
305
+ throw new Error(
306
+ 'Did not expect findHostNodesForHotUpdate to be called in production.',
307
+ );
308
+ }
309
+}
310
+
311
+function findHostNodesForMatchingFibersRecursively(
312
+ fiber: Fiber,
313
+ types: Set<any>,
314
+ hostNodes: Set<Instance>,
315
+) {
316
+ if (__DEV__) {
317
+ const {child, sibling, tag, type} = fiber;
318
+
319
+ let candidateType = null;
320
+ switch (tag) {
321
+ case FunctionComponent:
322
+ case SimpleMemoComponent:
323
+ case ClassComponent:
324
+ candidateType = type;
325
+ break;
326
+ case ForwardRef:
327
+ candidateType = type.render;
328
+ break;
329
+ default:
330
+ break;
331
+ }
332
+
333
+ let didMatch = false;
334
+ if (candidateType !== null) {
335
+ if (types.has(candidateType)) {
336
+ didMatch = true;
337
+ }
338
+ }
339
+
340
+ if (didMatch) {
341
+ // We have a match. This only drills down to the closest host components.
342
+ // There's no need to search deeper because for the purpose of giving
343
+ // visual feedback, "flashing" outermost parent rectangles is sufficient.
344
+ findHostNodesForFiberShallowly(fiber, hostNodes);
345
+ } else {
346
+ // If there's no match, maybe there will be one further down in the child tree.
347
+ if (child !== null) {
348
+ findHostNodesForMatchingFibersRecursively(child, types, hostNodes);
349
+ }
350
+ }
351
+
352
+ if (sibling !== null) {
353
+ findHostNodesForMatchingFibersRecursively(sibling, types, hostNodes);
354
+ }
355
+ }
356
+}
357
+
358
+function findHostNodesForFiberShallowly(
359
+ fiber: Fiber,
360
+ hostNodes: Set<Instance>,
361
+): void {
362
+ if (__DEV__) {
363
+ const foundHostNodes = findChildHostNodesForFiberShallowly(
364
+ fiber,
365
+ hostNodes,
366
+ );
367
+ if (foundHostNodes) {
368
+ return;
369
+ }
370
+ // If we didn't find any host children, fallback to closest host parent.
371
+ let node = fiber;
372
+ while (true) {
373
+ switch (node.tag) {
374
+ case HostComponent:
375
+ hostNodes.add(node.stateNode);
376
+ return;
377
+ case HostPortal:
378
+ hostNodes.add(node.stateNode.containerInfo);
379
+ return;
380
+ case HostRoot:
381
+ hostNodes.add(node.stateNode.containerInfo);
382
+ return;
383
+ }
384
+ if (node.return === null) {
385
+ throw new Error('Expected to reach root first.');
386
+ }
387
+ node = node.return;
388
+ }
389
+ }
390
+}
391
+
392
+function findChildHostNodesForFiberShallowly(
393
+ fiber: Fiber,
394
+ hostNodes: Set<Instance>,
395
+): boolean {
396
+ if (__DEV__) {
397
+ let node: Fiber = fiber;
398
+ let foundHostNodes = false;
399
+ while (true) {
400
+ if (node.tag === HostComponent) {
401
+ // We got a match.
402
+ foundHostNodes = true;
403
+ hostNodes.add(node.stateNode);
404
+ // There may still be more, so keep searching.
405
+ } else if (node.child !== null) {
406
+ node.child.return = node;
407
+ node = node.child;
408
+ continue;
409
+ }
410
+ if (node === fiber) {
411
+ return foundHostNodes;
412
+ }
413
+ while (node.sibling === null) {
414
+ if (node.return === null || node.return === fiber) {
415
+ return foundHostNodes;
416
+ }
417
+ node = node.return;
418
+ }
419
+ node.sibling.return = node.return;
420
+ node = node.sibling;
421
+ }
422
+ }
423
+ return false;
424
+}
packages/react-reconciler/src/ReactFiberReconciler.js
+5
-1
@@ -70,7 +70,10 @@ import {StrictMode} from './ReactTypeOfMode';
70
import {Sync} from './ReactFiberExpirationTime';
71
import {revertPassiveEffectsChange} from 'shared/ReactFeatureFlags';
72
import {requestCurrentSuspenseConfig} from './ReactFiberSuspenseConfig';
73
-import {scheduleHotUpdate} from './ReactFiberHotReloading';
73
+import {
74
+ scheduleHotUpdate,
75
+ findHostNodesForHotUpdate,
76
+} from './ReactFiberHotReloading';
77
78
type OpaqueRoot = FiberRoot;
79
@@ -472,6 +475,7 @@ export function injectIntoDevTools(devToolsConfig: DevToolsConfig): boolean {
475
476
return injectInternals({
477
...devToolsConfig,
478
+ findHostNodesForHotUpdate: __DEV__ ? findHostNodesForHotUpdate : null,
479
scheduleHotUpdate: __DEV__ ? scheduleHotUpdate : null,
480
overrideHookState,
481
overrideProps,