main
js 913 lines 27.2 KB
Raw
1 /**
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
5 * LICENSE file in the root directory of this source tree.
6 *
7 * @flow
8 */
9
10 import type {
11 Fiber,
12 FiberRoot,
13 SuspenseHydrationCallbacks,
14 TransitionTracingCallbacks,
15 } from './ReactInternalTypes';
16 import type {RootTag} from './ReactRootTags';
17 import type {
18 Container,
19 PublicInstance,
20 RendererInspectionConfig,
21 } from './ReactFiberConfig';
22 import type {ReactNodeList, ReactFormState} from 'shared/ReactTypes';
23 import type {Lane} from './ReactFiberLane';
24 import type {ActivityState} from './ReactFiberActivityComponent';
25 import type {SuspenseState} from './ReactFiberSuspenseComponent';
26
27 import {LegacyRoot} from './ReactRootTags';
28 import {
29 findCurrentHostFiber,
30 findCurrentHostFiberWithNoPortals,
31 } from './ReactFiberTreeReflection';
32 import {get as getInstance} from 'shared/ReactInstanceMap';
33 import {
34 HostComponent,
35 HostSingleton,
36 ClassComponent,
37 HostRoot,
38 SuspenseComponent,
39 ActivityComponent,
40 } from './ReactWorkTags';
41 import getComponentNameFromFiber from 'react-reconciler/src/getComponentNameFromFiber';
42 import isArray from 'shared/isArray';
43 import {
44 enableSchedulingProfiler,
45 disableLegacyMode,
46 } from 'shared/ReactFeatureFlags';
47 import ReactSharedInternals from 'shared/ReactSharedInternals';
48 import {
49 getPublicInstance,
50 rendererVersion,
51 rendererPackageName,
52 extraDevToolsConfig,
53 } from './ReactFiberConfig';
54 import {
55 findCurrentUnmaskedContext,
56 processChildContext,
57 emptyContextObject,
58 isContextProvider as isLegacyContextProvider,
59 } from './ReactFiberLegacyContext';
60 import {createFiberRoot} from './ReactFiberRoot';
61 import {isRootDehydrated} from './ReactFiberShellHydration';
62 import {
63 injectInternals,
64 markRenderScheduled,
65 onScheduleRoot,
66 injectProfilingHooks,
67 } from './ReactFiberDevToolsHook';
68 import {startUpdateTimerByLane} from './ReactProfilerTimer';
69 import {
70 requestUpdateLane,
71 scheduleUpdateOnFiber,
72 scheduleInitialHydrationOnRoot,
73 flushRoot,
74 batchedUpdates,
75 flushSyncFromReconciler,
76 flushSyncWork,
77 isAlreadyRendering,
78 deferredUpdates,
79 discreteUpdates,
80 flushPendingEffects,
81 } from './ReactFiberWorkLoop';
82 import {enqueueConcurrentRenderForLane} from './ReactFiberConcurrentUpdates';
83 import {
84 createUpdate,
85 enqueueUpdate,
86 entangleTransitions,
87 } from './ReactFiberClassUpdateQueue';
88 import {
89 isRendering as ReactCurrentFiberIsRendering,
90 current as ReactCurrentFiberCurrent,
91 runWithFiberInDEV,
92 } from './ReactCurrentFiber';
93 import {StrictLegacyMode} from './ReactTypeOfMode';
94 import {
95 SyncLane,
96 SelectiveHydrationLane,
97 getHighestPriorityPendingLanes,
98 higherPriorityLane,
99 getBumpedLaneForHydrationByLane,
100 claimNextRetryLane,
101 } from './ReactFiberLane';
102 import {
103 scheduleRefresh,
104 scheduleRoot,
105 setRefreshHandler,
106 } from './ReactFiberHotReloading';
107 import ReactVersion from 'shared/ReactVersion';
108 export {createPortal} from './ReactPortal';
109 export {
110 createComponentSelector,
111 createHasPseudoClassSelector,
112 createRoleSelector,
113 createTestNameSelector,
114 createTextSelector,
115 getFindAllNodesFailureDescription,
116 findAllNodes,
117 findBoundingRects,
118 focusWithin,
119 observeVisibleRects,
120 } from './ReactTestSelectors';
121 export {startHostTransition} from './ReactFiberHooks';
122 export {
123 defaultOnUncaughtError,
124 defaultOnCaughtError,
125 defaultOnRecoverableError,
126 } from './ReactFiberErrorLogger';
127 import {getLabelForLane, TotalLanes} from 'react-reconciler/src/ReactFiberLane';
128 import {registerDefaultIndicator} from './ReactFiberAsyncAction';
129
130 type OpaqueRoot = FiberRoot;
131
132 let didWarnAboutNestedUpdates;
133 let didWarnAboutFindNodeInStrictMode;
134
135 if (__DEV__) {
136 didWarnAboutNestedUpdates = false;
137 didWarnAboutFindNodeInStrictMode = {} as {[string]: boolean};
138 }
139
140 function getContextForSubtree(
141 parentComponent: ?component(...props: any),
142 ): Object {
143 if (!parentComponent) {
144 return emptyContextObject;
145 }
146
147 const fiber = getInstance(parentComponent);
148 const parentContext = findCurrentUnmaskedContext(fiber);
149
150 if (fiber.tag === ClassComponent) {
151 const Component = fiber.type;
152 if (isLegacyContextProvider(Component)) {
153 return processChildContext(fiber, Component, parentContext);
154 }
155 }
156
157 return parentContext;
158 }
159
160 function findHostInstance(component: Object): PublicInstance | null {
161 const fiber = getInstance(component);
162 if (fiber === undefined) {
163 if (typeof component.render === 'function') {
164 throw new Error('Unable to find node on an unmounted component.');
165 } else {
166 const keys = Object.keys(component).join(',');
167 throw new Error(
168 `Argument appears to not be a ReactComponent. Keys: ${keys}`,
169 );
170 }
171 }
172 const hostFiber = findCurrentHostFiber(fiber);
173 if (hostFiber === null) {
174 return null;
175 }
176 return getPublicInstance(hostFiber.stateNode);
177 }
178
179 function findHostInstanceWithWarning(
180 component: Object,
181 methodName: string,
182 ): PublicInstance | null {
183 if (__DEV__) {
184 const fiber = getInstance(component);
185 if (fiber === undefined) {
186 if (typeof component.render === 'function') {
187 throw new Error('Unable to find node on an unmounted component.');
188 } else {
189 const keys = Object.keys(component).join(',');
190 throw new Error(
191 `Argument appears to not be a ReactComponent. Keys: ${keys}`,
192 );
193 }
194 }
195 const hostFiber = findCurrentHostFiber(fiber);
196 if (hostFiber === null) {
197 return null;
198 }
199 if (hostFiber.mode & StrictLegacyMode) {
200 const componentName = getComponentNameFromFiber(fiber) || 'Component';
201 if (!didWarnAboutFindNodeInStrictMode[componentName]) {
202 didWarnAboutFindNodeInStrictMode[componentName] = true;
203 runWithFiberInDEV(hostFiber, () => {
204 if (fiber.mode & StrictLegacyMode) {
205 console.error(
206 '%s is deprecated in StrictMode. ' +
207 '%s was passed an instance of %s which is inside StrictMode. ' +
208 'Instead, add a ref directly to the element you want to reference. ' +
209 'Learn more about using refs safely here: ' +
210 'https://react.dev/link/strict-mode-find-node',
211 methodName,
212 methodName,
213 componentName,
214 );
215 } else {
216 console.error(
217 '%s is deprecated in StrictMode. ' +
218 '%s was passed an instance of %s which renders StrictMode children. ' +
219 'Instead, add a ref directly to the element you want to reference. ' +
220 'Learn more about using refs safely here: ' +
221 'https://react.dev/link/strict-mode-find-node',
222 methodName,
223 methodName,
224 componentName,
225 );
226 }
227 });
228 }
229 }
230 return getPublicInstance(hostFiber.stateNode);
231 }
232 return findHostInstance(component);
233 }
234
235 export function createContainer(
236 containerInfo: Container,
237 tag: RootTag,
238 hydrationCallbacks: null | SuspenseHydrationCallbacks,
239 isStrictMode: boolean,
240 // TODO: Remove `concurrentUpdatesByDefaultOverride`. It is now ignored.
241 concurrentUpdatesByDefaultOverride: null | boolean,
242 identifierPrefix: string,
243 onUncaughtError: (
244 error: mixed,
245 errorInfo: {+componentStack?: ?string},
246 ) => void,
247 onCaughtError: (
248 error: mixed,
249 errorInfo: {
250 +componentStack?: ?string,
251 +errorBoundary?: ?component(...props: any),
252 },
253 ) => void,
254 onRecoverableError: (
255 error: mixed,
256 errorInfo: {+componentStack?: ?string},
257 ) => void,
258 onDefaultTransitionIndicator: () => void | (() => void),
259 transitionCallbacks: null | TransitionTracingCallbacks,
260 ): OpaqueRoot {
261 const hydrate = false;
262 const initialChildren = null;
263 const root = createFiberRoot(
264 containerInfo,
265 tag,
266 hydrate,
267 initialChildren,
268 hydrationCallbacks,
269 isStrictMode,
270 identifierPrefix,
271 null,
272 onUncaughtError,
273 onCaughtError,
274 onRecoverableError,
275 onDefaultTransitionIndicator,
276 transitionCallbacks,
277 );
278 registerDefaultIndicator(onDefaultTransitionIndicator);
279 return root;
280 }
281
282 export function createHydrationContainer(
283 initialChildren: ReactNodeList,
284 // TODO: Remove `callback` when we delete legacy mode.
285 callback: ?Function,
286 containerInfo: Container,
287 tag: RootTag,
288 hydrationCallbacks: null | SuspenseHydrationCallbacks,
289 isStrictMode: boolean,
290 // TODO: Remove `concurrentUpdatesByDefaultOverride`. It is now ignored.
291 concurrentUpdatesByDefaultOverride: null | boolean,
292 identifierPrefix: string,
293 onUncaughtError: (
294 error: mixed,
295 errorInfo: {+componentStack?: ?string},
296 ) => void,
297 onCaughtError: (
298 error: mixed,
299 errorInfo: {
300 +componentStack?: ?string,
301 +errorBoundary?: ?component(...props: any),
302 },
303 ) => void,
304 onRecoverableError: (
305 error: mixed,
306 errorInfo: {+componentStack?: ?string},
307 ) => void,
308 onDefaultTransitionIndicator: () => void | (() => void),
309 transitionCallbacks: null | TransitionTracingCallbacks,
310 formState: ReactFormState<any, any> | null,
311 ): OpaqueRoot {
312 const hydrate = true;
313 const root = createFiberRoot(
314 containerInfo,
315 tag,
316 hydrate,
317 initialChildren,
318 hydrationCallbacks,
319 isStrictMode,
320 identifierPrefix,
321 formState,
322 onUncaughtError,
323 onCaughtError,
324 onRecoverableError,
325 onDefaultTransitionIndicator,
326 transitionCallbacks,
327 );
328
329 registerDefaultIndicator(onDefaultTransitionIndicator);
330
331 // TODO: Move this to FiberRoot constructor
332 root.context = getContextForSubtree(null);
333
334 // Schedule the initial render. In a hydration root, this is different from
335 // a regular update because the initial render must match was was rendered
336 // on the server.
337 // NOTE: This update intentionally doesn't have a payload. We're only using
338 // the update to schedule work on the root fiber (and, for legacy roots, to
339 // enqueue the callback if one is provided).
340 const current = root.current;
341 let lane = requestUpdateLane(current);
342 lane = getBumpedLaneForHydrationByLane(lane);
343 const update = createUpdate(lane);
344 update.callback =
345 callback !== undefined && callback !== null ? callback : null;
346 enqueueUpdate(current, update, lane);
347 startUpdateTimerByLane(lane, 'hydrateRoot()', null);
348 scheduleInitialHydrationOnRoot(root, lane);
349
350 return root;
351 }
352
353 export function updateContainer(
354 element: ReactNodeList,
355 container: OpaqueRoot,
356 parentComponent: ?component(...props: any),
357 callback: ?Function,
358 ): Lane {
359 const current = container.current;
360 const lane = requestUpdateLane(current);
361 updateContainerImpl(
362 current,
363 lane,
364 element,
365 container,
366 parentComponent,
367 callback,
368 );
369 return lane;
370 }
371
372 export function updateContainerSync(
373 element: ReactNodeList,
374 container: OpaqueRoot,
375 parentComponent: ?component(...props: any),
376 callback: ?Function,
377 ): Lane {
378 if (!disableLegacyMode && container.tag === LegacyRoot) {
379 flushPendingEffects();
380 }
381 const current = container.current;
382 updateContainerImpl(
383 current,
384 SyncLane,
385 element,
386 container,
387 parentComponent,
388 callback,
389 );
390 return SyncLane;
391 }
392
393 function updateContainerImpl(
394 rootFiber: Fiber,
395 lane: Lane,
396 element: ReactNodeList,
397 container: OpaqueRoot,
398 parentComponent: ?component(...props: any),
399 callback: ?Function,
400 ): void {
401 if (__DEV__) {
402 onScheduleRoot(container, element);
403 }
404
405 if (enableSchedulingProfiler) {
406 markRenderScheduled(lane);
407 }
408
409 const context = getContextForSubtree(parentComponent);
410 if (container.context === null) {
411 container.context = context;
412 } else {
413 container.pendingContext = context;
414 }
415
416 if (__DEV__) {
417 if (
418 ReactCurrentFiberIsRendering &&
419 ReactCurrentFiberCurrent !== null &&
420 !didWarnAboutNestedUpdates
421 ) {
422 didWarnAboutNestedUpdates = true;
423 console.error(
424 'Render methods should be a pure function of props and state; ' +
425 'triggering nested component updates from render is not allowed. ' +
426 'If necessary, trigger nested updates in componentDidUpdate.\n\n' +
427 'Check the render method of %s.',
428 getComponentNameFromFiber(ReactCurrentFiberCurrent) || 'Unknown',
429 );
430 }
431 }
432
433 const update = createUpdate(lane);
434 // Caution: React DevTools currently depends on this property
435 // being called "element".
436 update.payload = {element};
437
438 callback = callback === undefined ? null : callback;
439 if (callback !== null) {
440 if (__DEV__) {
441 if (typeof callback !== 'function') {
442 console.error(
443 'Expected the last optional `callback` argument to be a ' +
444 'function. Instead received: %s.',
445 callback,
446 );
447 }
448 }
449 update.callback = callback;
450 }
451
452 const root = enqueueUpdate(rootFiber, update, lane);
453 if (root !== null) {
454 startUpdateTimerByLane(lane, 'root.render()', null);
455 scheduleUpdateOnFiber(root, rootFiber, lane);
456 entangleTransitions(root, rootFiber, lane);
457 }
458 }
459
460 export {
461 batchedUpdates,
462 deferredUpdates,
463 discreteUpdates,
464 flushSyncFromReconciler,
465 flushSyncWork,
466 isAlreadyRendering,
467 flushPendingEffects as flushPassiveEffects,
468 };
469
470 export function getPublicRootInstance(
471 container: OpaqueRoot,
472 ): component(...props: any) | PublicInstance | null {
473 const containerFiber = container.current;
474 if (!containerFiber.child) {
475 return null;
476 }
477 switch (containerFiber.child.tag) {
478 case HostSingleton:
479 case HostComponent:
480 return getPublicInstance(containerFiber.child.stateNode);
481 default:
482 return containerFiber.child.stateNode;
483 }
484 }
485
486 export function attemptSynchronousHydration(fiber: Fiber): void {
487 switch (fiber.tag) {
488 case HostRoot: {
489 const root: FiberRoot = fiber.stateNode;
490 if (isRootDehydrated(root)) {
491 // Flush the first scheduled "update".
492 const lanes = getHighestPriorityPendingLanes(root);
493 flushRoot(root, lanes);
494 }
495 break;
496 }
497 case ActivityComponent:
498 case SuspenseComponent: {
499 const root = enqueueConcurrentRenderForLane(fiber, SyncLane);
500 if (root !== null) {
501 scheduleUpdateOnFiber(root, fiber, SyncLane);
502 }
503 flushSyncWork();
504 // If we're still blocked after this, we need to increase
505 // the priority of any promises resolving within this
506 // boundary so that they next attempt also has higher pri.
507 const retryLane = SyncLane;
508 markRetryLaneIfNotHydrated(fiber, retryLane);
509 break;
510 }
511 }
512 }
513
514 function markRetryLaneImpl(fiber: Fiber, retryLane: Lane) {
515 const suspenseState: null | SuspenseState | ActivityState =
516 fiber.memoizedState;
517 if (suspenseState !== null && suspenseState.dehydrated !== null) {
518 suspenseState.retryLane = higherPriorityLane(
519 suspenseState.retryLane,
520 retryLane,
521 );
522 }
523 }
524
525 // Increases the priority of thenables when they resolve within this boundary.
526 function markRetryLaneIfNotHydrated(fiber: Fiber, retryLane: Lane) {
527 markRetryLaneImpl(fiber, retryLane);
528 const alternate = fiber.alternate;
529 if (alternate) {
530 markRetryLaneImpl(alternate, retryLane);
531 }
532 }
533
534 export function attemptContinuousHydration(fiber: Fiber): void {
535 if (fiber.tag !== SuspenseComponent && fiber.tag !== ActivityComponent) {
536 // We ignore HostRoots here because we can't increase
537 // their priority and they should not suspend on I/O,
538 // since you have to wrap anything that might suspend in
539 // Suspense.
540 return;
541 }
542 const lane = SelectiveHydrationLane;
543 const root = enqueueConcurrentRenderForLane(fiber, lane);
544 if (root !== null) {
545 scheduleUpdateOnFiber(root, fiber, lane);
546 }
547 markRetryLaneIfNotHydrated(fiber, lane);
548 }
549
550 export function attemptHydrationAtCurrentPriority(fiber: Fiber): void {
551 if (fiber.tag !== SuspenseComponent && fiber.tag !== ActivityComponent) {
552 // We ignore HostRoots here because we can't increase
553 // their priority other than synchronously flush it.
554 return;
555 }
556 let lane = requestUpdateLane(fiber);
557 lane = getBumpedLaneForHydrationByLane(lane);
558 const root = enqueueConcurrentRenderForLane(fiber, lane);
559 if (root !== null) {
560 scheduleUpdateOnFiber(root, fiber, lane);
561 }
562 markRetryLaneIfNotHydrated(fiber, lane);
563 }
564
565 export {findHostInstance};
566
567 export {findHostInstanceWithWarning};
568
569 export function findHostInstanceWithNoPortals(
570 fiber: Fiber,
571 ): PublicInstance | null {
572 const hostFiber = findCurrentHostFiberWithNoPortals(fiber);
573 if (hostFiber === null) {
574 return null;
575 }
576 return getPublicInstance(hostFiber.stateNode);
577 }
578
579 let shouldErrorImpl: Fiber => ?boolean = fiber => null;
580
581 export function shouldError(fiber: Fiber): ?boolean {
582 return shouldErrorImpl(fiber);
583 }
584
585 let shouldSuspendImpl = (fiber: Fiber) => false;
586
587 export function shouldSuspend(fiber: Fiber): boolean {
588 return shouldSuspendImpl(fiber);
589 }
590
591 let overrideHookState = null;
592 let overrideHookStateDeletePath = null;
593 let overrideHookStateRenamePath = null;
594 let overrideProps = null;
595 let overridePropsDeletePath = null;
596 let overridePropsRenamePath = null;
597 let scheduleUpdate = null;
598 let scheduleRetry = null;
599 let setErrorHandler = null;
600 let setSuspenseHandler = null;
601
602 if (__DEV__) {
603 const copyWithDeleteImpl = (
604 obj: Object | Array<any>,
605 path: Array<string | number>,
606 index: number,
607 ): $FlowFixMe => {
608 const key = path[index];
609 const updated = isArray(obj) ? obj.slice() : {...obj};
610 if (index + 1 === path.length) {
611 if (isArray(updated)) {
612 updated.splice(key as any as number, 1);
613 } else {
614 delete updated[key];
615 }
616 return updated;
617 }
618 // $FlowFixMe[incompatible-use] number or string is fine here
619 updated[key] = copyWithDeleteImpl(obj[key], path, index + 1);
620 return updated;
621 };
622
623 const copyWithDelete = (
624 obj: Object | Array<any>,
625 path: Array<string | number>,
626 ): Object | Array<any> => {
627 return copyWithDeleteImpl(obj, path, 0);
628 };
629
630 const copyWithRenameImpl = (
631 obj: Object | Array<any>,
632 oldPath: Array<string | number>,
633 newPath: Array<string | number>,
634 index: number,
635 ): $FlowFixMe => {
636 const oldKey = oldPath[index];
637 const updated = isArray(obj) ? obj.slice() : {...obj};
638 if (index + 1 === oldPath.length) {
639 const newKey = newPath[index];
640 // $FlowFixMe[incompatible-use] number or string is fine here
641 updated[newKey] = updated[oldKey];
642 if (isArray(updated)) {
643 updated.splice(oldKey as any as number, 1);
644 } else {
645 delete updated[oldKey];
646 }
647 } else {
648 // $FlowFixMe[incompatible-use] number or string is fine here
649 updated[oldKey] = copyWithRenameImpl(
650 // $FlowFixMe[incompatible-use] number or string is fine here
651 obj[oldKey],
652 oldPath,
653 newPath,
654 index + 1,
655 );
656 }
657 return updated;
658 };
659
660 const copyWithRename = (
661 obj: Object | Array<any>,
662 oldPath: Array<string | number>,
663 newPath: Array<string | number>,
664 ): Object | Array<any> => {
665 if (oldPath.length !== newPath.length) {
666 console.warn('copyWithRename() expects paths of the same length');
667 return;
668 } else {
669 for (let i = 0; i < newPath.length - 1; i++) {
670 if (oldPath[i] !== newPath[i]) {
671 console.warn(
672 'copyWithRename() expects paths to be the same except for the deepest key',
673 );
674 return;
675 }
676 }
677 }
678 return copyWithRenameImpl(obj, oldPath, newPath, 0);
679 };
680
681 const copyWithSetImpl = (
682 obj: Object | Array<any>,
683 path: Array<string | number>,
684 index: number,
685 value: any,
686 ): $FlowFixMe => {
687 if (index >= path.length) {
688 return value;
689 }
690 const key = path[index];
691 const updated = isArray(obj) ? obj.slice() : {...obj};
692 // $FlowFixMe[incompatible-use] number or string is fine here
693 updated[key] = copyWithSetImpl(obj[key], path, index + 1, value);
694 return updated;
695 };
696
697 const copyWithSet = (
698 obj: Object | Array<any>,
699 path: Array<string | number>,
700 value: any,
701 ): Object | Array<any> => {
702 return copyWithSetImpl(obj, path, 0, value);
703 };
704
705 const findHook = (fiber: Fiber, id: number) => {
706 // For now, the "id" of stateful hooks is just the stateful hook index.
707 // This may change in the future with e.g. nested hooks.
708 let currentHook = fiber.memoizedState;
709 while (currentHook !== null && id > 0) {
710 currentHook = currentHook.next;
711 id--;
712 }
713 return currentHook;
714 };
715
716 // Support DevTools editable values for useState and useReducer.
717 overrideHookState = (
718 fiber: Fiber,
719 id: number,
720 path: Array<string | number>,
721 value: any,
722 ) => {
723 const hook = findHook(fiber, id);
724 if (hook !== null) {
725 const newState = copyWithSet(hook.memoizedState, path, value);
726 hook.memoizedState = newState;
727 hook.baseState = newState;
728
729 // We aren't actually adding an update to the queue,
730 // because there is no update we can add for useReducer hooks that won't trigger an error.
731 // (There's no appropriate action type for DevTools overrides.)
732 // As a result though, React will see the scheduled update as a noop and bailout.
733 // Shallow cloning props works as a workaround for now to bypass the bailout check.
734 fiber.memoizedProps = {...fiber.memoizedProps};
735
736 const root = enqueueConcurrentRenderForLane(fiber, SyncLane);
737 if (root !== null) {
738 scheduleUpdateOnFiber(root, fiber, SyncLane);
739 }
740 }
741 };
742 overrideHookStateDeletePath = (
743 fiber: Fiber,
744 id: number,
745 path: Array<string | number>,
746 ) => {
747 const hook = findHook(fiber, id);
748 if (hook !== null) {
749 const newState = copyWithDelete(hook.memoizedState, path);
750 hook.memoizedState = newState;
751 hook.baseState = newState;
752
753 // We aren't actually adding an update to the queue,
754 // because there is no update we can add for useReducer hooks that won't trigger an error.
755 // (There's no appropriate action type for DevTools overrides.)
756 // As a result though, React will see the scheduled update as a noop and bailout.
757 // Shallow cloning props works as a workaround for now to bypass the bailout check.
758 fiber.memoizedProps = {...fiber.memoizedProps};
759
760 const root = enqueueConcurrentRenderForLane(fiber, SyncLane);
761 if (root !== null) {
762 scheduleUpdateOnFiber(root, fiber, SyncLane);
763 }
764 }
765 };
766 overrideHookStateRenamePath = (
767 fiber: Fiber,
768 id: number,
769 oldPath: Array<string | number>,
770 newPath: Array<string | number>,
771 ) => {
772 const hook = findHook(fiber, id);
773 if (hook !== null) {
774 const newState = copyWithRename(hook.memoizedState, oldPath, newPath);
775 hook.memoizedState = newState;
776 hook.baseState = newState;
777
778 // We aren't actually adding an update to the queue,
779 // because there is no update we can add for useReducer hooks that won't trigger an error.
780 // (There's no appropriate action type for DevTools overrides.)
781 // As a result though, React will see the scheduled update as a noop and bailout.
782 // Shallow cloning props works as a workaround for now to bypass the bailout check.
783 fiber.memoizedProps = {...fiber.memoizedProps};
784
785 const root = enqueueConcurrentRenderForLane(fiber, SyncLane);
786 if (root !== null) {
787 scheduleUpdateOnFiber(root, fiber, SyncLane);
788 }
789 }
790 };
791
792 // Support DevTools props for function components, forwardRef, memo, host components, etc.
793 overrideProps = (fiber: Fiber, path: Array<string | number>, value: any) => {
794 fiber.pendingProps = copyWithSet(fiber.memoizedProps, path, value);
795 if (fiber.alternate) {
796 fiber.alternate.pendingProps = fiber.pendingProps;
797 }
798 const root = enqueueConcurrentRenderForLane(fiber, SyncLane);
799 if (root !== null) {
800 scheduleUpdateOnFiber(root, fiber, SyncLane);
801 }
802 };
803 overridePropsDeletePath = (fiber: Fiber, path: Array<string | number>) => {
804 fiber.pendingProps = copyWithDelete(fiber.memoizedProps, path);
805 if (fiber.alternate) {
806 fiber.alternate.pendingProps = fiber.pendingProps;
807 }
808 const root = enqueueConcurrentRenderForLane(fiber, SyncLane);
809 if (root !== null) {
810 scheduleUpdateOnFiber(root, fiber, SyncLane);
811 }
812 };
813 overridePropsRenamePath = (
814 fiber: Fiber,
815 oldPath: Array<string | number>,
816 newPath: Array<string | number>,
817 ) => {
818 fiber.pendingProps = copyWithRename(fiber.memoizedProps, oldPath, newPath);
819 if (fiber.alternate) {
820 fiber.alternate.pendingProps = fiber.pendingProps;
821 }
822 const root = enqueueConcurrentRenderForLane(fiber, SyncLane);
823 if (root !== null) {
824 scheduleUpdateOnFiber(root, fiber, SyncLane);
825 }
826 };
827
828 scheduleUpdate = (fiber: Fiber) => {
829 const root = enqueueConcurrentRenderForLane(fiber, SyncLane);
830 if (root !== null) {
831 scheduleUpdateOnFiber(root, fiber, SyncLane);
832 }
833 };
834
835 scheduleRetry = (fiber: Fiber) => {
836 const lane = claimNextRetryLane();
837 const root = enqueueConcurrentRenderForLane(fiber, lane);
838 if (root !== null) {
839 scheduleUpdateOnFiber(root, fiber, lane);
840 }
841 };
842
843 setErrorHandler = (newShouldErrorImpl: Fiber => ?boolean) => {
844 shouldErrorImpl = newShouldErrorImpl;
845 };
846
847 setSuspenseHandler = (newShouldSuspendImpl: Fiber => boolean) => {
848 shouldSuspendImpl = newShouldSuspendImpl;
849 };
850 }
851
852 function getCurrentFiberForDevTools() {
853 return ReactCurrentFiberCurrent;
854 }
855
856 function getLaneLabelMap(): Map<Lane, string> | null {
857 if (enableSchedulingProfiler) {
858 const map: Map<Lane, string> = new Map();
859
860 let lane = 1;
861 for (let index = 0; index < TotalLanes; index++) {
862 const label = getLabelForLane(lane) as any as string;
863 map.set(lane, label);
864 lane *= 2;
865 }
866
867 return map;
868 } else {
869 return null;
870 }
871 }
872
873 export function injectIntoDevTools(): boolean {
874 const internals: Object = {
875 bundleType: __DEV__ ? 1 : 0, // Might add PROFILE later.
876 version: rendererVersion,
877 rendererPackageName: rendererPackageName,
878 currentDispatcherRef: ReactSharedInternals,
879 // Enables DevTools to detect reconciler version rather than renderer version
880 // which may not match for third party renderers.
881 reconcilerVersion: ReactVersion,
882 };
883 // $FlowFixMe[invalid-compare]
884 if (extraDevToolsConfig !== null) {
885 internals.rendererConfig = extraDevToolsConfig as RendererInspectionConfig;
886 }
887 if (__DEV__) {
888 internals.overrideHookState = overrideHookState;
889 internals.overrideHookStateDeletePath = overrideHookStateDeletePath;
890 internals.overrideHookStateRenamePath = overrideHookStateRenamePath;
891 internals.overrideProps = overrideProps;
892 internals.overridePropsDeletePath = overridePropsDeletePath;
893 internals.overridePropsRenamePath = overridePropsRenamePath;
894 internals.scheduleUpdate = scheduleUpdate;
895 internals.scheduleRetry = scheduleRetry;
896 internals.setErrorHandler = setErrorHandler;
897 internals.setSuspenseHandler = setSuspenseHandler;
898 // React Refresh
899 internals.scheduleRefresh = scheduleRefresh;
900 internals.scheduleRoot = scheduleRoot;
901 internals.setRefreshHandler = setRefreshHandler;
902 // Enables DevTools to append owner stacks to error messages in DEV mode.
903 internals.getCurrentFiber = getCurrentFiberForDevTools;
904 }
905 if (enableSchedulingProfiler) {
906 // Conditionally inject these hooks only if Timeline profiler is supported by this build.
907 // This gives DevTools a way to feature detect that isn't tied to version number
908 // (since profiling and timeline are controlled by different feature flags).
909 internals.getLaneLabelMap = getLaneLabelMap;
910 internals.injectProfilingHooks = injectProfilingHooks;
911 }
912 return injectInternals(internals);
913 }