main
js 1,045 lines 31.5 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 ViewTransitionProps,
12 ProfilerProps,
13 ProfilerPhase,
14 } from 'shared/ReactTypes';
15 import type {Fiber} from './ReactInternalTypes';
16 import type {UpdateQueue} from './ReactFiberClassUpdateQueue';
17 import type {FunctionComponentUpdateQueue} from './ReactFiberHooks';
18 import type {HookFlags} from './ReactHookEffectTags';
19 import type {FragmentInstanceType} from './ReactFiberConfig';
20 import type {ViewTransitionState} from './ReactFiberViewTransitionComponent';
21
22 import {getViewTransitionName} from './ReactFiberViewTransitionComponent';
23
24 import {
25 enableProfilerTimer,
26 enableProfilerCommitHooks,
27 enableProfilerNestedUpdatePhase,
28 enableSchedulingProfiler,
29 enableViewTransition,
30 enableFragmentRefs,
31 } from 'shared/ReactFeatureFlags';
32 import {
33 ClassComponent,
34 Fragment,
35 HostComponent,
36 HostHoistable,
37 HostSingleton,
38 ViewTransitionComponent,
39 } from './ReactWorkTags';
40 import {NoFlags} from './ReactFiberFlags';
41 import getComponentNameFromFiber from 'react-reconciler/src/getComponentNameFromFiber';
42 import {resolveClassComponentProps} from './ReactFiberClassComponent';
43 import {
44 recordEffectDuration,
45 startEffectTimer,
46 isCurrentUpdateNested,
47 } from './ReactProfilerTimer';
48 import {NoMode, ProfileMode} from './ReactTypeOfMode';
49 import {
50 commitCallbacks,
51 commitHiddenCallbacks,
52 } from './ReactFiberClassUpdateQueue';
53 import {
54 getPublicInstance,
55 createViewTransitionInstance,
56 createFragmentInstance,
57 } from './ReactFiberConfig';
58 import {
59 captureCommitPhaseError,
60 setIsRunningInsertionEffect,
61 } from './ReactFiberWorkLoop';
62 import {
63 NoFlags as NoHookEffect,
64 Layout as HookLayout,
65 Insertion as HookInsertion,
66 Passive as HookPassive,
67 } from './ReactHookEffectTags';
68 import {didWarnAboutReassigningProps} from './ReactFiberBeginWork';
69 import {
70 markComponentPassiveEffectMountStarted,
71 markComponentPassiveEffectMountStopped,
72 markComponentPassiveEffectUnmountStarted,
73 markComponentPassiveEffectUnmountStopped,
74 markComponentLayoutEffectMountStarted,
75 markComponentLayoutEffectMountStopped,
76 markComponentLayoutEffectUnmountStarted,
77 markComponentLayoutEffectUnmountStopped,
78 } from './ReactFiberDevToolsHook';
79 import {
80 callComponentDidMountInDEV,
81 callComponentDidUpdateInDEV,
82 callComponentWillUnmountInDEV,
83 callCreateInDEV,
84 callDestroyInDEV,
85 } from './ReactFiberCallUserSpace';
86
87 import {runWithFiberInDEV} from './ReactCurrentFiber';
88
89 function shouldProfile(current: Fiber): boolean {
90 return (
91 enableProfilerTimer &&
92 enableProfilerCommitHooks &&
93 (current.mode & ProfileMode) !== NoMode
94 );
95 }
96
97 export function commitHookLayoutEffects(
98 finishedWork: Fiber,
99 hookFlags: HookFlags,
100 ) {
101 // At this point layout effects have already been destroyed (during mutation phase).
102 // This is done to prevent sibling component effects from interfering with each other,
103 // e.g. a destroy function in one component should never override a ref set
104 // by a create function in another component during the same commit.
105 if (shouldProfile(finishedWork)) {
106 startEffectTimer();
107 commitHookEffectListMount(hookFlags, finishedWork);
108 recordEffectDuration(finishedWork);
109 } else {
110 commitHookEffectListMount(hookFlags, finishedWork);
111 }
112 }
113
114 export function commitHookLayoutUnmountEffects(
115 finishedWork: Fiber,
116 nearestMountedAncestor: null | Fiber,
117 hookFlags: HookFlags,
118 ) {
119 // Layout effects are destroyed during the mutation phase so that all
120 // destroy functions for all fibers are called before any create functions.
121 // This prevents sibling component effects from interfering with each other,
122 // e.g. a destroy function in one component should never override a ref set
123 // by a create function in another component during the same commit.
124 if (shouldProfile(finishedWork)) {
125 startEffectTimer();
126 commitHookEffectListUnmount(
127 hookFlags,
128 finishedWork,
129 nearestMountedAncestor,
130 );
131 recordEffectDuration(finishedWork);
132 } else {
133 commitHookEffectListUnmount(
134 hookFlags,
135 finishedWork,
136 nearestMountedAncestor,
137 );
138 }
139 }
140
141 export function commitHookEffectListMount(
142 flags: HookFlags,
143 finishedWork: Fiber,
144 ) {
145 try {
146 const updateQueue: FunctionComponentUpdateQueue | null =
147 finishedWork.updateQueue as any;
148 const lastEffect = updateQueue !== null ? updateQueue.lastEffect : null;
149 if (lastEffect !== null) {
150 const firstEffect = lastEffect.next;
151 let effect = firstEffect;
152 do {
153 if ((effect.tag & flags) === flags) {
154 if (enableSchedulingProfiler) {
155 if ((flags & HookPassive) !== NoHookEffect) {
156 markComponentPassiveEffectMountStarted(finishedWork);
157 } else if ((flags & HookLayout) !== NoHookEffect) {
158 markComponentLayoutEffectMountStarted(finishedWork);
159 }
160 }
161
162 // Mount
163 let destroy;
164 if (__DEV__) {
165 if ((flags & HookInsertion) !== NoHookEffect) {
166 setIsRunningInsertionEffect(true);
167 }
168 destroy = runWithFiberInDEV(finishedWork, callCreateInDEV, effect);
169 if ((flags & HookInsertion) !== NoHookEffect) {
170 setIsRunningInsertionEffect(false);
171 }
172 } else {
173 const create = effect.create;
174 const inst = effect.inst;
175 destroy = create();
176 inst.destroy = destroy;
177 }
178
179 if (enableSchedulingProfiler) {
180 if ((flags & HookPassive) !== NoHookEffect) {
181 markComponentPassiveEffectMountStopped();
182 } else if ((flags & HookLayout) !== NoHookEffect) {
183 markComponentLayoutEffectMountStopped();
184 }
185 }
186
187 if (__DEV__) {
188 if (destroy !== undefined && typeof destroy !== 'function') {
189 let hookName;
190 if ((effect.tag & HookLayout) !== NoFlags) {
191 hookName = 'useLayoutEffect';
192 } else if ((effect.tag & HookInsertion) !== NoFlags) {
193 hookName = 'useInsertionEffect';
194 } else {
195 hookName = 'useEffect';
196 }
197 let addendum;
198 // $FlowFixMe[invalid-compare]
199 if (destroy === null) {
200 addendum =
201 ' You returned null. If your effect does not require clean ' +
202 'up, return undefined (or nothing).';
203 // $FlowFixMe[incompatible-type] (@poteto) this check is safe on arbitrary non-null/void objects
204 } else if (typeof destroy.then === 'function') {
205 addendum =
206 '\n\nIt looks like you wrote ' +
207 hookName +
208 '(async () => ...) or returned a Promise. ' +
209 'Instead, write the async function inside your effect ' +
210 'and call it immediately:\n\n' +
211 hookName +
212 '(() => {\n' +
213 ' async function fetchData() {\n' +
214 ' // You can await here\n' +
215 ' const response = await MyAPI.getData(someId);\n' +
216 ' // ...\n' +
217 ' }\n' +
218 ' fetchData();\n' +
219 `}, [someId]); // Or [] if effect doesn't need props or state\n\n` +
220 'Learn more about data fetching with Hooks: https://react.dev/link/hooks-data-fetching';
221 } else {
222 // $FlowFixMe[unsafe-addition] (@poteto)
223 addendum = ' You returned: ' + destroy;
224 }
225 runWithFiberInDEV(
226 finishedWork,
227 (n, a) => {
228 console.error(
229 '%s must not return anything besides a function, ' +
230 'which is used for clean-up.%s',
231 n,
232 a,
233 );
234 },
235 hookName,
236 addendum,
237 );
238 }
239 }
240 }
241 effect = effect.next;
242 } while (effect !== firstEffect);
243 }
244 } catch (error) {
245 captureCommitPhaseError(finishedWork, finishedWork.return, error);
246 }
247 }
248
249 export function commitHookEffectListUnmount(
250 flags: HookFlags,
251 finishedWork: Fiber,
252 nearestMountedAncestor: Fiber | null,
253 ) {
254 try {
255 const updateQueue: FunctionComponentUpdateQueue | null =
256 finishedWork.updateQueue as any;
257 const lastEffect = updateQueue !== null ? updateQueue.lastEffect : null;
258 if (lastEffect !== null) {
259 const firstEffect = lastEffect.next;
260 let effect = firstEffect;
261 do {
262 if ((effect.tag & flags) === flags) {
263 // Unmount
264 const inst = effect.inst;
265 const destroy = inst.destroy;
266 if (destroy !== undefined) {
267 inst.destroy = undefined;
268 if (enableSchedulingProfiler) {
269 if ((flags & HookPassive) !== NoHookEffect) {
270 markComponentPassiveEffectUnmountStarted(finishedWork);
271 } else if ((flags & HookLayout) !== NoHookEffect) {
272 markComponentLayoutEffectUnmountStarted(finishedWork);
273 }
274 }
275
276 if (__DEV__) {
277 if ((flags & HookInsertion) !== NoHookEffect) {
278 setIsRunningInsertionEffect(true);
279 }
280 }
281 safelyCallDestroy(finishedWork, nearestMountedAncestor, destroy);
282 if (__DEV__) {
283 if ((flags & HookInsertion) !== NoHookEffect) {
284 setIsRunningInsertionEffect(false);
285 }
286 }
287
288 if (enableSchedulingProfiler) {
289 if ((flags & HookPassive) !== NoHookEffect) {
290 markComponentPassiveEffectUnmountStopped();
291 } else if ((flags & HookLayout) !== NoHookEffect) {
292 markComponentLayoutEffectUnmountStopped();
293 }
294 }
295 }
296 }
297 effect = effect.next;
298 } while (effect !== firstEffect);
299 }
300 } catch (error) {
301 captureCommitPhaseError(finishedWork, finishedWork.return, error);
302 }
303 }
304
305 export function commitHookPassiveMountEffects(
306 finishedWork: Fiber,
307 hookFlags: HookFlags,
308 ) {
309 if (shouldProfile(finishedWork)) {
310 startEffectTimer();
311 commitHookEffectListMount(hookFlags, finishedWork);
312 recordEffectDuration(finishedWork);
313 } else {
314 commitHookEffectListMount(hookFlags, finishedWork);
315 }
316 }
317
318 export function commitHookPassiveUnmountEffects(
319 finishedWork: Fiber,
320 nearestMountedAncestor: null | Fiber,
321 hookFlags: HookFlags,
322 ) {
323 if (shouldProfile(finishedWork)) {
324 startEffectTimer();
325 commitHookEffectListUnmount(
326 hookFlags,
327 finishedWork,
328 nearestMountedAncestor,
329 );
330 recordEffectDuration(finishedWork);
331 } else {
332 commitHookEffectListUnmount(
333 hookFlags,
334 finishedWork,
335 nearestMountedAncestor,
336 );
337 }
338 }
339
340 export function commitClassLayoutLifecycles(
341 finishedWork: Fiber,
342 current: Fiber | null,
343 ) {
344 const instance = finishedWork.stateNode;
345 if (current === null) {
346 // We could update instance props and state here,
347 // but instead we rely on them being set during last render.
348 // TODO: revisit this when we implement resuming.
349 if (__DEV__) {
350 if (
351 !finishedWork.type.defaultProps &&
352 !('ref' in finishedWork.memoizedProps) &&
353 !didWarnAboutReassigningProps
354 ) {
355 if (instance.props !== finishedWork.memoizedProps) {
356 console.error(
357 'Expected %s props to match memoized props before ' +
358 'componentDidMount. ' +
359 'This might either be because of a bug in React, or because ' +
360 'a component reassigns its own `this.props`. ' +
361 'Please file an issue.',
362 getComponentNameFromFiber(finishedWork) || 'instance',
363 );
364 }
365 if (instance.state !== finishedWork.memoizedState) {
366 console.error(
367 'Expected %s state to match memoized state before ' +
368 'componentDidMount. ' +
369 'This might either be because of a bug in React, or because ' +
370 'a component reassigns its own `this.state`. ' +
371 'Please file an issue.',
372 getComponentNameFromFiber(finishedWork) || 'instance',
373 );
374 }
375 }
376 }
377 if (shouldProfile(finishedWork)) {
378 startEffectTimer();
379 if (__DEV__) {
380 runWithFiberInDEV(
381 finishedWork,
382 callComponentDidMountInDEV,
383 finishedWork,
384 instance,
385 );
386 } else {
387 try {
388 instance.componentDidMount();
389 } catch (error) {
390 captureCommitPhaseError(finishedWork, finishedWork.return, error);
391 }
392 }
393 recordEffectDuration(finishedWork);
394 } else {
395 if (__DEV__) {
396 runWithFiberInDEV(
397 finishedWork,
398 callComponentDidMountInDEV,
399 finishedWork,
400 instance,
401 );
402 } else {
403 try {
404 instance.componentDidMount();
405 } catch (error) {
406 captureCommitPhaseError(finishedWork, finishedWork.return, error);
407 }
408 }
409 }
410 } else {
411 const prevProps = resolveClassComponentProps(
412 finishedWork.type,
413 current.memoizedProps,
414 );
415 const prevState = current.memoizedState;
416 // We could update instance props and state here,
417 // but instead we rely on them being set during last render.
418 // TODO: revisit this when we implement resuming.
419 if (__DEV__) {
420 if (
421 !finishedWork.type.defaultProps &&
422 !('ref' in finishedWork.memoizedProps) &&
423 !didWarnAboutReassigningProps
424 ) {
425 if (instance.props !== finishedWork.memoizedProps) {
426 console.error(
427 'Expected %s props to match memoized props before ' +
428 'componentDidUpdate. ' +
429 'This might either be because of a bug in React, or because ' +
430 'a component reassigns its own `this.props`. ' +
431 'Please file an issue.',
432 getComponentNameFromFiber(finishedWork) || 'instance',
433 );
434 }
435 if (instance.state !== finishedWork.memoizedState) {
436 console.error(
437 'Expected %s state to match memoized state before ' +
438 'componentDidUpdate. ' +
439 'This might either be because of a bug in React, or because ' +
440 'a component reassigns its own `this.state`. ' +
441 'Please file an issue.',
442 getComponentNameFromFiber(finishedWork) || 'instance',
443 );
444 }
445 }
446 }
447 if (shouldProfile(finishedWork)) {
448 startEffectTimer();
449 if (__DEV__) {
450 runWithFiberInDEV(
451 finishedWork,
452 callComponentDidUpdateInDEV,
453 finishedWork,
454 instance,
455 prevProps,
456 prevState,
457 instance.__reactInternalSnapshotBeforeUpdate,
458 );
459 } else {
460 try {
461 instance.componentDidUpdate(
462 prevProps,
463 prevState,
464 instance.__reactInternalSnapshotBeforeUpdate,
465 );
466 } catch (error) {
467 captureCommitPhaseError(finishedWork, finishedWork.return, error);
468 }
469 }
470 recordEffectDuration(finishedWork);
471 } else {
472 if (__DEV__) {
473 runWithFiberInDEV(
474 finishedWork,
475 callComponentDidUpdateInDEV,
476 finishedWork,
477 instance,
478 prevProps,
479 prevState,
480 instance.__reactInternalSnapshotBeforeUpdate,
481 );
482 } else {
483 try {
484 instance.componentDidUpdate(
485 prevProps,
486 prevState,
487 instance.__reactInternalSnapshotBeforeUpdate,
488 );
489 } catch (error) {
490 captureCommitPhaseError(finishedWork, finishedWork.return, error);
491 }
492 }
493 }
494 }
495 }
496
497 export function commitClassDidMount(finishedWork: Fiber) {
498 // TODO: Check for LayoutStatic flag
499 const instance = finishedWork.stateNode;
500 if (typeof instance.componentDidMount === 'function') {
501 if (__DEV__) {
502 runWithFiberInDEV(
503 finishedWork,
504 callComponentDidMountInDEV,
505 finishedWork,
506 instance,
507 );
508 } else {
509 try {
510 instance.componentDidMount();
511 } catch (error) {
512 captureCommitPhaseError(finishedWork, finishedWork.return, error);
513 }
514 }
515 }
516 }
517
518 export function commitClassCallbacks(finishedWork: Fiber) {
519 // TODO: I think this is now always non-null by the time it reaches the
520 // commit phase. Consider removing the type check.
521 const updateQueue: UpdateQueue<mixed> | null =
522 finishedWork.updateQueue as any;
523 if (updateQueue !== null) {
524 const instance = finishedWork.stateNode;
525 if (__DEV__) {
526 if (
527 !finishedWork.type.defaultProps &&
528 !('ref' in finishedWork.memoizedProps) &&
529 !didWarnAboutReassigningProps
530 ) {
531 if (instance.props !== finishedWork.memoizedProps) {
532 console.error(
533 'Expected %s props to match memoized props before ' +
534 'processing the update queue. ' +
535 'This might either be because of a bug in React, or because ' +
536 'a component reassigns its own `this.props`. ' +
537 'Please file an issue.',
538 getComponentNameFromFiber(finishedWork) || 'instance',
539 );
540 }
541 if (instance.state !== finishedWork.memoizedState) {
542 console.error(
543 'Expected %s state to match memoized state before ' +
544 'processing the update queue. ' +
545 'This might either be because of a bug in React, or because ' +
546 'a component reassigns its own `this.state`. ' +
547 'Please file an issue.',
548 getComponentNameFromFiber(finishedWork) || 'instance',
549 );
550 }
551 }
552 }
553 // We could update instance props and state here,
554 // but instead we rely on them being set during last render.
555 // TODO: revisit this when we implement resuming.
556 try {
557 if (__DEV__) {
558 runWithFiberInDEV(finishedWork, commitCallbacks, updateQueue, instance);
559 } else {
560 commitCallbacks(updateQueue, instance);
561 }
562 } catch (error) {
563 captureCommitPhaseError(finishedWork, finishedWork.return, error);
564 }
565 }
566 }
567
568 export function commitClassHiddenCallbacks(finishedWork: Fiber) {
569 // Commit any callbacks that would have fired while the component
570 // was hidden.
571 const updateQueue: UpdateQueue<mixed> | null =
572 finishedWork.updateQueue as any;
573 if (updateQueue !== null) {
574 const instance = finishedWork.stateNode;
575 try {
576 if (__DEV__) {
577 runWithFiberInDEV(
578 finishedWork,
579 commitHiddenCallbacks,
580 updateQueue,
581 instance,
582 );
583 } else {
584 commitHiddenCallbacks(updateQueue, instance);
585 }
586 } catch (error) {
587 captureCommitPhaseError(finishedWork, finishedWork.return, error);
588 }
589 }
590 }
591
592 export function commitRootCallbacks(finishedWork: Fiber) {
593 // TODO: I think this is now always non-null by the time it reaches the
594 // commit phase. Consider removing the type check.
595 const updateQueue: UpdateQueue<mixed> | null =
596 finishedWork.updateQueue as any;
597 if (updateQueue !== null) {
598 let instance = null;
599 if (finishedWork.child !== null) {
600 switch (finishedWork.child.tag) {
601 case HostSingleton:
602 case HostComponent:
603 instance = getPublicInstance(finishedWork.child.stateNode);
604 break;
605 case ClassComponent:
606 instance = finishedWork.child.stateNode;
607 break;
608 }
609 }
610 try {
611 if (__DEV__) {
612 runWithFiberInDEV(finishedWork, commitCallbacks, updateQueue, instance);
613 } else {
614 commitCallbacks(updateQueue, instance);
615 }
616 } catch (error) {
617 captureCommitPhaseError(finishedWork, finishedWork.return, error);
618 }
619 }
620 }
621
622 let didWarnAboutUndefinedSnapshotBeforeUpdate: Set<mixed> | null = null;
623 if (__DEV__) {
624 didWarnAboutUndefinedSnapshotBeforeUpdate = new Set();
625 }
626
627 function callGetSnapshotBeforeUpdates(
628 instance: any,
629 prevProps: any,
630 prevState: any,
631 ) {
632 return instance.getSnapshotBeforeUpdate(prevProps, prevState);
633 }
634
635 export function commitClassSnapshot(finishedWork: Fiber, current: Fiber) {
636 const prevProps = current.memoizedProps;
637 const prevState = current.memoizedState;
638 const instance = finishedWork.stateNode;
639 // We could update instance props and state here,
640 // but instead we rely on them being set during last render.
641 // TODO: revisit this when we implement resuming.
642 if (__DEV__) {
643 if (
644 !finishedWork.type.defaultProps &&
645 !('ref' in finishedWork.memoizedProps) &&
646 !didWarnAboutReassigningProps
647 ) {
648 if (instance.props !== finishedWork.memoizedProps) {
649 console.error(
650 'Expected %s props to match memoized props before ' +
651 'getSnapshotBeforeUpdate. ' +
652 'This might either be because of a bug in React, or because ' +
653 'a component reassigns its own `this.props`. ' +
654 'Please file an issue.',
655 getComponentNameFromFiber(finishedWork) || 'instance',
656 );
657 }
658 if (instance.state !== finishedWork.memoizedState) {
659 console.error(
660 'Expected %s state to match memoized state before ' +
661 'getSnapshotBeforeUpdate. ' +
662 'This might either be because of a bug in React, or because ' +
663 'a component reassigns its own `this.state`. ' +
664 'Please file an issue.',
665 getComponentNameFromFiber(finishedWork) || 'instance',
666 );
667 }
668 }
669 }
670 try {
671 const resolvedPrevProps = resolveClassComponentProps(
672 finishedWork.type,
673 prevProps,
674 );
675 let snapshot;
676 if (__DEV__) {
677 snapshot = runWithFiberInDEV(
678 finishedWork,
679 callGetSnapshotBeforeUpdates,
680 instance,
681 resolvedPrevProps,
682 prevState,
683 );
684 const didWarnSet =
685 didWarnAboutUndefinedSnapshotBeforeUpdate as any as Set<mixed>;
686 if (snapshot === undefined && !didWarnSet.has(finishedWork.type)) {
687 didWarnSet.add(finishedWork.type);
688 runWithFiberInDEV(finishedWork, () => {
689 console.error(
690 '%s.getSnapshotBeforeUpdate(): A snapshot value (or null) ' +
691 'must be returned. You have returned undefined.',
692 getComponentNameFromFiber(finishedWork),
693 );
694 });
695 }
696 } else {
697 snapshot = callGetSnapshotBeforeUpdates(
698 instance,
699 resolvedPrevProps,
700 prevState,
701 );
702 }
703 instance.__reactInternalSnapshotBeforeUpdate = snapshot;
704 } catch (error) {
705 captureCommitPhaseError(finishedWork, finishedWork.return, error);
706 }
707 }
708
709 // Capture errors so they don't interrupt unmounting.
710 export function safelyCallComponentWillUnmount(
711 current: Fiber,
712 nearestMountedAncestor: Fiber | null,
713 instance: any,
714 ) {
715 instance.props = resolveClassComponentProps(
716 current.type,
717 current.memoizedProps,
718 );
719 instance.state = current.memoizedState;
720 if (shouldProfile(current)) {
721 startEffectTimer();
722 if (__DEV__) {
723 runWithFiberInDEV(
724 current,
725 callComponentWillUnmountInDEV,
726 current,
727 nearestMountedAncestor,
728 instance,
729 );
730 } else {
731 try {
732 instance.componentWillUnmount();
733 } catch (error) {
734 captureCommitPhaseError(current, nearestMountedAncestor, error);
735 }
736 }
737 recordEffectDuration(current);
738 } else {
739 if (__DEV__) {
740 runWithFiberInDEV(
741 current,
742 callComponentWillUnmountInDEV,
743 current,
744 nearestMountedAncestor,
745 instance,
746 );
747 } else {
748 try {
749 instance.componentWillUnmount();
750 } catch (error) {
751 captureCommitPhaseError(current, nearestMountedAncestor, error);
752 }
753 }
754 }
755 }
756
757 function commitAttachRef(finishedWork: Fiber) {
758 const ref = finishedWork.ref;
759 if (ref !== null) {
760 let instanceToUse;
761 switch (finishedWork.tag) {
762 case HostHoistable:
763 case HostSingleton:
764 case HostComponent:
765 instanceToUse = getPublicInstance(finishedWork.stateNode);
766 break;
767 case ViewTransitionComponent: {
768 if (enableViewTransition) {
769 const instance: ViewTransitionState = finishedWork.stateNode;
770 const props: ViewTransitionProps = finishedWork.memoizedProps;
771 const name = getViewTransitionName(props, instance);
772 if (instance.ref === null || instance.ref.name !== name) {
773 instance.ref = createViewTransitionInstance(name);
774 }
775 instanceToUse = instance.ref;
776 break;
777 }
778 instanceToUse = finishedWork.stateNode;
779 break;
780 }
781 case Fragment:
782 if (enableFragmentRefs) {
783 const instance: null | FragmentInstanceType = finishedWork.stateNode;
784 if (instance === null) {
785 finishedWork.stateNode = createFragmentInstance(finishedWork);
786 }
787 instanceToUse = finishedWork.stateNode;
788 break;
789 }
790 // Fallthrough
791 default:
792 instanceToUse = finishedWork.stateNode;
793 }
794 if (typeof ref === 'function') {
795 if (shouldProfile(finishedWork)) {
796 try {
797 startEffectTimer();
798 finishedWork.refCleanup = ref(instanceToUse);
799 } finally {
800 recordEffectDuration(finishedWork);
801 }
802 } else {
803 finishedWork.refCleanup = ref(instanceToUse);
804 }
805 } else {
806 if (__DEV__) {
807 // TODO: We should move these warnings to happen during the render
808 // phase (markRef).
809 if (typeof ref === 'string') {
810 console.error('String refs are no longer supported.');
811 } else if (!ref.hasOwnProperty('current')) {
812 console.error(
813 'Unexpected ref object provided for %s. ' +
814 'Use either a ref-setter function or React.createRef().',
815 getComponentNameFromFiber(finishedWork),
816 );
817 }
818 }
819
820 // $FlowFixMe[incompatible-use] unable to narrow type to the non-function case
821 ref.current = instanceToUse;
822 }
823 }
824 }
825
826 // Capture errors so they don't interrupt mounting.
827 export function safelyAttachRef(
828 current: Fiber,
829 nearestMountedAncestor: Fiber | null,
830 ) {
831 try {
832 if (__DEV__) {
833 runWithFiberInDEV(current, commitAttachRef, current);
834 } else {
835 commitAttachRef(current);
836 }
837 } catch (error) {
838 captureCommitPhaseError(current, nearestMountedAncestor, error);
839 }
840 }
841
842 export function safelyDetachRef(
843 current: Fiber,
844 nearestMountedAncestor: Fiber | null,
845 ) {
846 const ref = current.ref;
847 const refCleanup = current.refCleanup;
848
849 if (ref !== null) {
850 if (typeof refCleanup === 'function') {
851 try {
852 if (shouldProfile(current)) {
853 try {
854 startEffectTimer();
855 if (__DEV__) {
856 runWithFiberInDEV(current, refCleanup);
857 } else {
858 refCleanup();
859 }
860 } finally {
861 recordEffectDuration(current);
862 }
863 } else {
864 if (__DEV__) {
865 runWithFiberInDEV(current, refCleanup);
866 } else {
867 refCleanup();
868 }
869 }
870 } catch (error) {
871 captureCommitPhaseError(current, nearestMountedAncestor, error);
872 } finally {
873 // `refCleanup` has been called. Nullify all references to it to prevent double invocation.
874 current.refCleanup = null;
875 const finishedWork = current.alternate;
876 if (finishedWork != null) {
877 finishedWork.refCleanup = null;
878 }
879 }
880 } else if (typeof ref === 'function') {
881 try {
882 if (shouldProfile(current)) {
883 try {
884 startEffectTimer();
885 if (__DEV__) {
886 runWithFiberInDEV(current, ref, null) as void;
887 } else {
888 ref(null);
889 }
890 } finally {
891 recordEffectDuration(current);
892 }
893 } else {
894 if (__DEV__) {
895 runWithFiberInDEV(current, ref, null) as void;
896 } else {
897 ref(null);
898 }
899 }
900 } catch (error) {
901 captureCommitPhaseError(current, nearestMountedAncestor, error);
902 }
903 } else {
904 // $FlowFixMe[incompatible-use] unable to narrow type to RefObject
905 ref.current = null;
906 }
907 }
908 }
909
910 function safelyCallDestroy(
911 current: Fiber,
912 nearestMountedAncestor: Fiber | null,
913 destroy: (() => void) | (({...}) => void),
914 resource?: {...} | void | null,
915 ) {
916 // $FlowFixMe[extra-arg] @poteto this is safe either way because the extra arg is ignored if it's not a CRUD effect
917 const destroy_ = resource == null ? destroy : destroy.bind(null, resource);
918 if (__DEV__) {
919 runWithFiberInDEV(
920 current,
921 callDestroyInDEV,
922 current,
923 nearestMountedAncestor,
924 destroy_,
925 );
926 } else {
927 try {
928 // $FlowFixMe[incompatible-type](incompatible-call) Already bound to resource
929 destroy_();
930 } catch (error) {
931 captureCommitPhaseError(current, nearestMountedAncestor, error);
932 }
933 }
934 }
935
936 function commitProfiler(
937 finishedWork: Fiber,
938 current: Fiber | null,
939 commitStartTime: number,
940 effectDuration: number,
941 ) {
942 const {id, onCommit, onRender} = finishedWork.memoizedProps as ProfilerProps;
943
944 let phase: ProfilerPhase = current === null ? 'mount' : 'update';
945 if (enableProfilerNestedUpdatePhase) {
946 if (isCurrentUpdateNested()) {
947 phase = 'nested-update';
948 }
949 }
950
951 if (typeof onRender === 'function') {
952 onRender(
953 id,
954 phase,
955 // $FlowFixMe[incompatible-type]: This should be always a number in profiling mode
956 finishedWork.actualDuration,
957 // $FlowFixMe[incompatible-type]: This should be always a number in profiling mode
958 finishedWork.treeBaseDuration,
959 // $FlowFixMe[incompatible-type]: This should be always a number in profiling mode
960 finishedWork.actualStartTime,
961 commitStartTime,
962 );
963 }
964
965 if (enableProfilerCommitHooks) {
966 if (typeof onCommit === 'function') {
967 onCommit(id, phase, effectDuration, commitStartTime);
968 }
969 }
970 }
971
972 export function commitProfilerUpdate(
973 finishedWork: Fiber,
974 current: Fiber | null,
975 commitStartTime: number,
976 effectDuration: number,
977 ) {
978 if (enableProfilerTimer) {
979 try {
980 if (__DEV__) {
981 runWithFiberInDEV(
982 finishedWork,
983 commitProfiler,
984 finishedWork,
985 current,
986 commitStartTime,
987 effectDuration,
988 );
989 } else {
990 commitProfiler(finishedWork, current, commitStartTime, effectDuration);
991 }
992 } catch (error) {
993 captureCommitPhaseError(finishedWork, finishedWork.return, error);
994 }
995 }
996 }
997
998 function commitProfilerPostCommitImpl(
999 finishedWork: Fiber,
1000 current: Fiber | null,
1001 commitStartTime: number,
1002 passiveEffectDuration: number,
1003 ): void {
1004 const {id, onPostCommit} = finishedWork.memoizedProps;
1005
1006 let phase = current === null ? 'mount' : 'update';
1007 if (enableProfilerNestedUpdatePhase) {
1008 if (isCurrentUpdateNested()) {
1009 phase = 'nested-update';
1010 }
1011 }
1012
1013 if (typeof onPostCommit === 'function') {
1014 onPostCommit(id, phase, passiveEffectDuration, commitStartTime);
1015 }
1016 }
1017
1018 export function commitProfilerPostCommit(
1019 finishedWork: Fiber,
1020 current: Fiber | null,
1021 commitStartTime: number,
1022 passiveEffectDuration: number,
1023 ) {
1024 try {
1025 if (__DEV__) {
1026 runWithFiberInDEV(
1027 finishedWork,
1028 commitProfilerPostCommitImpl,
1029 finishedWork,
1030 current,
1031 commitStartTime,
1032 passiveEffectDuration,
1033 );
1034 } else {
1035 commitProfilerPostCommitImpl(
1036 finishedWork,
1037 current,
1038 commitStartTime,
1039 passiveEffectDuration,
1040 );
1041 }
1042 } catch (error) {
1043 captureCommitPhaseError(finishedWork, finishedWork.return, error);
1044 }
1045 }