@samitouri / QOS-React / commits / fe03c56d1e

[Fiber] Extract Functions that Call User Space and Host Configs in Commit to Separate Modules (#30881)

This is mostly just moves and same code extracted into utility functions. This is to help clarify what needs to be wrapped in try/catch and runWithFiberInDEV. I'll do the runWithFiberInDEV changes in a follow up. This leaves ReactCommitWork mostly to do matching on the tag and the recursive loops.

Sebastian Markbåge committed Sep 5, 2024 at 20:53 UTC fe03c56d1e51379a18676b04cf185e76f04cd457
3 files changed +1459 -1220
packages/react-reconciler/src/ReactFiberCommitEffects.js new
+816
@@ -0,0 +1,816 @@
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 {Fiber} from './ReactInternalTypes';
11 +import type {UpdateQueue} from './ReactFiberClassUpdateQueue';
12 +import type {FunctionComponentUpdateQueue} from './ReactFiberHooks';
13 +import type {HookFlags} from './ReactHookEffectTags';
14 +
15 +import {
16 + enableProfilerTimer,
17 + enableProfilerCommitHooks,
18 + enableProfilerNestedUpdatePhase,
19 + enableSchedulingProfiler,
20 + enableScopeAPI,
21 + disableStringRefs,
22 +} from 'shared/ReactFeatureFlags';
23 +import {
24 + ClassComponent,
25 + HostComponent,
26 + HostHoistable,
27 + HostSingleton,
28 + ScopeComponent,
29 +} from './ReactWorkTags';
30 +import {NoFlags} from './ReactFiberFlags';
31 +import getComponentNameFromFiber from 'react-reconciler/src/getComponentNameFromFiber';
32 +import {resolveClassComponentProps} from './ReactFiberClassComponent';
33 +import {
34 + recordLayoutEffectDuration,
35 + startLayoutEffectTimer,
36 + recordPassiveEffectDuration,
37 + startPassiveEffectTimer,
38 + isCurrentUpdateNested,
39 +} from './ReactProfilerTimer';
40 +import {NoMode, ProfileMode} from './ReactTypeOfMode';
41 +import {
42 + commitCallbacks,
43 + commitHiddenCallbacks,
44 +} from './ReactFiberClassUpdateQueue';
45 +import {getPublicInstance} from './ReactFiberConfig';
46 +import {
47 + captureCommitPhaseError,
48 + setIsRunningInsertionEffect,
49 + getExecutionContext,
50 + CommitContext,
51 + NoContext,
52 +} from './ReactFiberWorkLoop';
53 +import {
54 + NoFlags as NoHookEffect,
55 + Layout as HookLayout,
56 + Insertion as HookInsertion,
57 + Passive as HookPassive,
58 +} from './ReactHookEffectTags';
59 +import {didWarnAboutReassigningProps} from './ReactFiberBeginWork';
60 +import {
61 + markComponentPassiveEffectMountStarted,
62 + markComponentPassiveEffectMountStopped,
63 + markComponentPassiveEffectUnmountStarted,
64 + markComponentPassiveEffectUnmountStopped,
65 + markComponentLayoutEffectMountStarted,
66 + markComponentLayoutEffectMountStopped,
67 + markComponentLayoutEffectUnmountStarted,
68 + markComponentLayoutEffectUnmountStopped,
69 +} from './ReactFiberDevToolsHook';
70 +import {
71 + callComponentDidMountInDEV,
72 + callComponentDidUpdateInDEV,
73 + callComponentWillUnmountInDEV,
74 + callCreateInDEV,
75 + callDestroyInDEV,
76 +} from './ReactFiberCallUserSpace';
77 +
78 +function shouldProfile(current: Fiber): boolean {
79 + return (
80 + enableProfilerTimer &&
81 + enableProfilerCommitHooks &&
82 + (current.mode & ProfileMode) !== NoMode &&
83 + (getExecutionContext() & CommitContext) !== NoContext
84 + );
85 +}
86 +
87 +export function commitHookLayoutEffects(
88 + finishedWork: Fiber,
89 + hookFlags: HookFlags,
90 +) {
91 + // At this point layout effects have already been destroyed (during mutation phase).
92 + // This is done to prevent sibling component effects from interfering with each other,
93 + // e.g. a destroy function in one component should never override a ref set
94 + // by a create function in another component during the same commit.
95 + if (shouldProfile(finishedWork)) {
96 + startLayoutEffectTimer();
97 + commitHookEffectListMount(hookFlags, finishedWork);
98 + recordLayoutEffectDuration(finishedWork);
99 + } else {
100 + commitHookEffectListMount(hookFlags, finishedWork);
101 + }
102 +}
103 +
104 +export function commitHookEffectListMount(
105 + flags: HookFlags,
106 + finishedWork: Fiber,
107 +) {
108 + try {
109 + const updateQueue: FunctionComponentUpdateQueue | null =
110 + (finishedWork.updateQueue: any);
111 + const lastEffect = updateQueue !== null ? updateQueue.lastEffect : null;
112 + if (lastEffect !== null) {
113 + const firstEffect = lastEffect.next;
114 + let effect = firstEffect;
115 + do {
116 + if ((effect.tag & flags) === flags) {
117 + if (enableSchedulingProfiler) {
118 + if ((flags & HookPassive) !== NoHookEffect) {
119 + markComponentPassiveEffectMountStarted(finishedWork);
120 + } else if ((flags & HookLayout) !== NoHookEffect) {
121 + markComponentLayoutEffectMountStarted(finishedWork);
122 + }
123 + }
124 +
125 + // Mount
126 + let destroy;
127 + if (__DEV__) {
128 + if ((flags & HookInsertion) !== NoHookEffect) {
129 + setIsRunningInsertionEffect(true);
130 + }
131 + destroy = callCreateInDEV(effect);
132 + if ((flags & HookInsertion) !== NoHookEffect) {
133 + setIsRunningInsertionEffect(false);
134 + }
135 + } else {
136 + const create = effect.create;
137 + const inst = effect.inst;
138 + destroy = create();
139 + inst.destroy = destroy;
140 + }
141 +
142 + if (enableSchedulingProfiler) {
143 + if ((flags & HookPassive) !== NoHookEffect) {
144 + markComponentPassiveEffectMountStopped();
145 + } else if ((flags & HookLayout) !== NoHookEffect) {
146 + markComponentLayoutEffectMountStopped();
147 + }
148 + }
149 +
150 + if (__DEV__) {
151 + if (destroy !== undefined && typeof destroy !== 'function') {
152 + let hookName;
153 + if ((effect.tag & HookLayout) !== NoFlags) {
154 + hookName = 'useLayoutEffect';
155 + } else if ((effect.tag & HookInsertion) !== NoFlags) {
156 + hookName = 'useInsertionEffect';
157 + } else {
158 + hookName = 'useEffect';
159 + }
160 + let addendum;
161 + if (destroy === null) {
162 + addendum =
163 + ' You returned null. If your effect does not require clean ' +
164 + 'up, return undefined (or nothing).';
165 + } else if (typeof destroy.then === 'function') {
166 + addendum =
167 + '\n\nIt looks like you wrote ' +
168 + hookName +
169 + '(async () => ...) or returned a Promise. ' +
170 + 'Instead, write the async function inside your effect ' +
171 + 'and call it immediately:\n\n' +
172 + hookName +
173 + '(() => {\n' +
174 + ' async function fetchData() {\n' +
175 + ' // You can await here\n' +
176 + ' const response = await MyAPI.getData(someId);\n' +
177 + ' // ...\n' +
178 + ' }\n' +
179 + ' fetchData();\n' +
180 + `}, [someId]); // Or [] if effect doesn't need props or state\n\n` +
181 + 'Learn more about data fetching with Hooks: https://react.dev/link/hooks-data-fetching';
182 + } else {
183 + addendum = ' You returned: ' + destroy;
184 + }
185 + console.error(
186 + '%s must not return anything besides a function, ' +
187 + 'which is used for clean-up.%s',
188 + hookName,
189 + addendum,
190 + );
191 + }
192 + }
193 + }
194 + effect = effect.next;
195 + } while (effect !== firstEffect);
196 + }
197 + } catch (error) {
198 + captureCommitPhaseError(finishedWork, finishedWork.return, error);
199 + }
200 +}
201 +
202 +export function commitHookEffectListUnmount(
203 + flags: HookFlags,
204 + finishedWork: Fiber,
205 + nearestMountedAncestor: Fiber | null,
206 +) {
207 + try {
208 + const updateQueue: FunctionComponentUpdateQueue | null =
209 + (finishedWork.updateQueue: any);
210 + const lastEffect = updateQueue !== null ? updateQueue.lastEffect : null;
211 + if (lastEffect !== null) {
212 + const firstEffect = lastEffect.next;
213 + let effect = firstEffect;
214 + do {
215 + if ((effect.tag & flags) === flags) {
216 + // Unmount
217 + const inst = effect.inst;
218 + const destroy = inst.destroy;
219 + if (destroy !== undefined) {
220 + inst.destroy = undefined;
221 + if (enableSchedulingProfiler) {
222 + if ((flags & HookPassive) !== NoHookEffect) {
223 + markComponentPassiveEffectUnmountStarted(finishedWork);
224 + } else if ((flags & HookLayout) !== NoHookEffect) {
225 + markComponentLayoutEffectUnmountStarted(finishedWork);
226 + }
227 + }
228 +
229 + if (__DEV__) {
230 + if ((flags & HookInsertion) !== NoHookEffect) {
231 + setIsRunningInsertionEffect(true);
232 + }
233 + }
234 + safelyCallDestroy(finishedWork, nearestMountedAncestor, destroy);
235 + if (__DEV__) {
236 + if ((flags & HookInsertion) !== NoHookEffect) {
237 + setIsRunningInsertionEffect(false);
238 + }
239 + }
240 +
241 + if (enableSchedulingProfiler) {
242 + if ((flags & HookPassive) !== NoHookEffect) {
243 + markComponentPassiveEffectUnmountStopped();
244 + } else if ((flags & HookLayout) !== NoHookEffect) {
245 + markComponentLayoutEffectUnmountStopped();
246 + }
247 + }
248 + }
249 + }
250 + effect = effect.next;
251 + } while (effect !== firstEffect);
252 + }
253 + } catch (error) {
254 + captureCommitPhaseError(finishedWork, finishedWork.return, error);
255 + }
256 +}
257 +
258 +export function commitHookPassiveMountEffects(
259 + finishedWork: Fiber,
260 + hookFlags: HookFlags,
261 +) {
262 + if (shouldProfile(finishedWork)) {
263 + startPassiveEffectTimer();
264 + commitHookEffectListMount(hookFlags, finishedWork);
265 + recordPassiveEffectDuration(finishedWork);
266 + } else {
267 + commitHookEffectListMount(hookFlags, finishedWork);
268 + }
269 +}
270 +
271 +export function commitHookPassiveUnmountEffects(
272 + finishedWork: Fiber,
273 + nearestMountedAncestor: null | Fiber,
274 + hookFlags: HookFlags,
275 +) {
276 + if (shouldProfile(finishedWork)) {
277 + startPassiveEffectTimer();
278 + commitHookEffectListUnmount(
279 + hookFlags,
280 + finishedWork,
281 + nearestMountedAncestor,
282 + );
283 + recordPassiveEffectDuration(finishedWork);
284 + } else {
285 + commitHookEffectListUnmount(
286 + hookFlags,
287 + finishedWork,
288 + nearestMountedAncestor,
289 + );
290 + }
291 +}
292 +
293 +export function commitClassLayoutLifecycles(
294 + finishedWork: Fiber,
295 + current: Fiber | null,
296 +) {
297 + const instance = finishedWork.stateNode;
298 + if (current === null) {
299 + // We could update instance props and state here,
300 + // but instead we rely on them being set during last render.
301 + // TODO: revisit this when we implement resuming.
302 + if (__DEV__) {
303 + if (
304 + !finishedWork.type.defaultProps &&
305 + !('ref' in finishedWork.memoizedProps) &&
306 + !didWarnAboutReassigningProps
307 + ) {
308 + if (instance.props !== finishedWork.memoizedProps) {
309 + console.error(
310 + 'Expected %s props to match memoized props before ' +
311 + 'componentDidMount. ' +
312 + 'This might either be because of a bug in React, or because ' +
313 + 'a component reassigns its own `this.props`. ' +
314 + 'Please file an issue.',
315 + getComponentNameFromFiber(finishedWork) || 'instance',
316 + );
317 + }
318 + if (instance.state !== finishedWork.memoizedState) {
319 + console.error(
320 + 'Expected %s state to match memoized state before ' +
321 + 'componentDidMount. ' +
322 + 'This might either be because of a bug in React, or because ' +
323 + 'a component reassigns its own `this.state`. ' +
324 + 'Please file an issue.',
325 + getComponentNameFromFiber(finishedWork) || 'instance',
326 + );
327 + }
328 + }
329 + }
330 + if (shouldProfile(finishedWork)) {
331 + startLayoutEffectTimer();
332 + if (__DEV__) {
333 + callComponentDidMountInDEV(finishedWork, instance);
334 + } else {
335 + try {
336 + instance.componentDidMount();
337 + } catch (error) {
338 + captureCommitPhaseError(finishedWork, finishedWork.return, error);
339 + }
340 + }
341 + recordLayoutEffectDuration(finishedWork);
342 + } else {
343 + if (__DEV__) {
344 + callComponentDidMountInDEV(finishedWork, instance);
345 + } else {
346 + try {
347 + instance.componentDidMount();
348 + } catch (error) {
349 + captureCommitPhaseError(finishedWork, finishedWork.return, error);
350 + }
351 + }
352 + }
353 + } else {
354 + const prevProps = resolveClassComponentProps(
355 + finishedWork.type,
356 + current.memoizedProps,
357 + finishedWork.elementType === finishedWork.type,
358 + );
359 + const prevState = current.memoizedState;
360 + // We could update instance props and state here,
361 + // but instead we rely on them being set during last render.
362 + // TODO: revisit this when we implement resuming.
363 + if (__DEV__) {
364 + if (
365 + !finishedWork.type.defaultProps &&
366 + !('ref' in finishedWork.memoizedProps) &&
367 + !didWarnAboutReassigningProps
368 + ) {
369 + if (instance.props !== finishedWork.memoizedProps) {
370 + console.error(
371 + 'Expected %s props to match memoized props before ' +
372 + 'componentDidUpdate. ' +
373 + 'This might either be because of a bug in React, or because ' +
374 + 'a component reassigns its own `this.props`. ' +
375 + 'Please file an issue.',
376 + getComponentNameFromFiber(finishedWork) || 'instance',
377 + );
378 + }
379 + if (instance.state !== finishedWork.memoizedState) {
380 + console.error(
381 + 'Expected %s state to match memoized state before ' +
382 + 'componentDidUpdate. ' +
383 + 'This might either be because of a bug in React, or because ' +
384 + 'a component reassigns its own `this.state`. ' +
385 + 'Please file an issue.',
386 + getComponentNameFromFiber(finishedWork) || 'instance',
387 + );
388 + }
389 + }
390 + }
391 + if (shouldProfile(finishedWork)) {
392 + startLayoutEffectTimer();
393 + if (__DEV__) {
394 + callComponentDidUpdateInDEV(
395 + finishedWork,
396 + instance,
397 + prevProps,
398 + prevState,
399 + instance.__reactInternalSnapshotBeforeUpdate,
400 + );
401 + } else {
402 + try {
403 + instance.componentDidUpdate(
404 + prevProps,
405 + prevState,
406 + instance.__reactInternalSnapshotBeforeUpdate,
407 + );
408 + } catch (error) {
409 + captureCommitPhaseError(finishedWork, finishedWork.return, error);
410 + }
411 + }
412 + recordLayoutEffectDuration(finishedWork);
413 + } else {
414 + if (__DEV__) {
415 + callComponentDidUpdateInDEV(
416 + finishedWork,
417 + instance,
418 + prevProps,
419 + prevState,
420 + instance.__reactInternalSnapshotBeforeUpdate,
421 + );
422 + } else {
423 + try {
424 + instance.componentDidUpdate(
425 + prevProps,
426 + prevState,
427 + instance.__reactInternalSnapshotBeforeUpdate,
428 + );
429 + } catch (error) {
430 + captureCommitPhaseError(finishedWork, finishedWork.return, error);
431 + }
432 + }
433 + }
434 + }
435 +}
436 +
437 +export function commitClassDidMount(finishedWork: Fiber) {
438 + // TODO: Check for LayoutStatic flag
439 + const instance = finishedWork.stateNode;
440 + if (typeof instance.componentDidMount === 'function') {
441 + if (__DEV__) {
442 + callComponentDidMountInDEV(finishedWork, instance);
443 + } else {
444 + try {
445 + instance.componentDidMount();
446 + } catch (error) {
447 + captureCommitPhaseError(finishedWork, finishedWork.return, error);
448 + }
449 + }
450 + }
451 +}
452 +
453 +export function commitClassCallbacks(finishedWork: Fiber) {
454 + // TODO: I think this is now always non-null by the time it reaches the
455 + // commit phase. Consider removing the type check.
456 + const updateQueue: UpdateQueue<mixed> | null =
457 + (finishedWork.updateQueue: any);
458 + if (updateQueue !== null) {
459 + const instance = finishedWork.stateNode;
460 + if (__DEV__) {
461 + if (
462 + !finishedWork.type.defaultProps &&
463 + !('ref' in finishedWork.memoizedProps) &&
464 + !didWarnAboutReassigningProps
465 + ) {
466 + if (instance.props !== finishedWork.memoizedProps) {
467 + console.error(
468 + 'Expected %s props to match memoized props before ' +
469 + 'processing the update queue. ' +
470 + 'This might either be because of a bug in React, or because ' +
471 + 'a component reassigns its own `this.props`. ' +
472 + 'Please file an issue.',
473 + getComponentNameFromFiber(finishedWork) || 'instance',
474 + );
475 + }
476 + if (instance.state !== finishedWork.memoizedState) {
477 + console.error(
478 + 'Expected %s state to match memoized state before ' +
479 + 'processing the update queue. ' +
480 + 'This might either be because of a bug in React, or because ' +
481 + 'a component reassigns its own `this.state`. ' +
482 + 'Please file an issue.',
483 + getComponentNameFromFiber(finishedWork) || 'instance',
484 + );
485 + }
486 + }
487 + }
488 + // We could update instance props and state here,
489 + // but instead we rely on them being set during last render.
490 + // TODO: revisit this when we implement resuming.
491 + try {
492 + commitCallbacks(updateQueue, instance);
493 + } catch (error) {
494 + captureCommitPhaseError(finishedWork, finishedWork.return, error);
495 + }
496 + }
497 +}
498 +
499 +export function commitClassHiddenCallbacks(finishedWork: Fiber) {
500 + // Commit any callbacks that would have fired while the component
501 + // was hidden.
502 + const updateQueue: UpdateQueue<mixed> | null =
503 + (finishedWork.updateQueue: any);
504 + if (updateQueue !== null) {
505 + const instance = finishedWork.stateNode;
506 + try {
507 + commitHiddenCallbacks(updateQueue, instance);
508 + } catch (error) {
509 + captureCommitPhaseError(finishedWork, finishedWork.return, error);
510 + }
511 + }
512 +}
513 +
514 +export function commitRootCallbacks(finishedWork: Fiber) {
515 + // TODO: I think this is now always non-null by the time it reaches the
516 + // commit phase. Consider removing the type check.
517 + const updateQueue: UpdateQueue<mixed> | null =
518 + (finishedWork.updateQueue: any);
519 + if (updateQueue !== null) {
520 + let instance = null;
521 + if (finishedWork.child !== null) {
522 + switch (finishedWork.child.tag) {
523 + case HostSingleton:
524 + case HostComponent:
525 + instance = getPublicInstance(finishedWork.child.stateNode);
526 + break;
527 + case ClassComponent:
528 + instance = finishedWork.child.stateNode;
529 + break;
530 + }
531 + }
532 + try {
533 + commitCallbacks(updateQueue, instance);
534 + } catch (error) {
535 + captureCommitPhaseError(finishedWork, finishedWork.return, error);
536 + }
537 + }
538 +}
539 +
540 +let didWarnAboutUndefinedSnapshotBeforeUpdate: Set<mixed> | null = null;
541 +if (__DEV__) {
542 + didWarnAboutUndefinedSnapshotBeforeUpdate = new Set();
543 +}
544 +
545 +export function commitClassSnapshot(finishedWork: Fiber, current: Fiber) {
546 + const prevProps = current.memoizedProps;
547 + const prevState = current.memoizedState;
548 + const instance = finishedWork.stateNode;
549 + // We could update instance props and state here,
550 + // but instead we rely on them being set during last render.
551 + // TODO: revisit this when we implement resuming.
552 + if (__DEV__) {
553 + if (
554 + !finishedWork.type.defaultProps &&
555 + !('ref' in finishedWork.memoizedProps) &&
556 + !didWarnAboutReassigningProps
557 + ) {
558 + if (instance.props !== finishedWork.memoizedProps) {
559 + console.error(
560 + 'Expected %s props to match memoized props before ' +
561 + 'getSnapshotBeforeUpdate. ' +
562 + 'This might either be because of a bug in React, or because ' +
563 + 'a component reassigns its own `this.props`. ' +
564 + 'Please file an issue.',
565 + getComponentNameFromFiber(finishedWork) || 'instance',
566 + );
567 + }
568 + if (instance.state !== finishedWork.memoizedState) {
569 + console.error(
570 + 'Expected %s state to match memoized state before ' +
571 + 'getSnapshotBeforeUpdate. ' +
572 + 'This might either be because of a bug in React, or because ' +
573 + 'a component reassigns its own `this.state`. ' +
574 + 'Please file an issue.',
575 + getComponentNameFromFiber(finishedWork) || 'instance',
576 + );
577 + }
578 + }
579 + }
580 + try {
581 + const snapshot = instance.getSnapshotBeforeUpdate(
582 + resolveClassComponentProps(
583 + finishedWork.type,
584 + prevProps,
585 + finishedWork.elementType === finishedWork.type,
586 + ),
587 + prevState,
588 + );
589 + if (__DEV__) {
590 + const didWarnSet =
591 + ((didWarnAboutUndefinedSnapshotBeforeUpdate: any): Set<mixed>);
592 + if (snapshot === undefined && !didWarnSet.has(finishedWork.type)) {
593 + didWarnSet.add(finishedWork.type);
594 + console.error(
595 + '%s.getSnapshotBeforeUpdate(): A snapshot value (or null) ' +
596 + 'must be returned. You have returned undefined.',
597 + getComponentNameFromFiber(finishedWork),
598 + );
599 + }
600 + }
601 + instance.__reactInternalSnapshotBeforeUpdate = snapshot;
602 + } catch (error) {
603 + captureCommitPhaseError(finishedWork, finishedWork.return, error);
604 + }
605 +}
606 +
607 +// Capture errors so they don't interrupt unmounting.
608 +export function safelyCallComponentWillUnmount(
609 + current: Fiber,
610 + nearestMountedAncestor: Fiber | null,
611 + instance: any,
612 +) {
613 + instance.props = resolveClassComponentProps(
614 + current.type,
615 + current.memoizedProps,
616 + current.elementType === current.type,
617 + );
618 + instance.state = current.memoizedState;
619 + if (shouldProfile(current)) {
620 + startLayoutEffectTimer();
621 + if (__DEV__) {
622 + callComponentWillUnmountInDEV(current, nearestMountedAncestor, instance);
623 + } else {
624 + try {
625 + instance.componentWillUnmount();
626 + } catch (error) {
627 + captureCommitPhaseError(current, nearestMountedAncestor, error);
628 + }
629 + }
630 + recordLayoutEffectDuration(current);
631 + } else {
632 + if (__DEV__) {
633 + callComponentWillUnmountInDEV(current, nearestMountedAncestor, instance);
634 + } else {
635 + try {
636 + instance.componentWillUnmount();
637 + } catch (error) {
638 + captureCommitPhaseError(current, nearestMountedAncestor, error);
639 + }
640 + }
641 + }
642 +}
643 +
644 +function commitAttachRef(finishedWork: Fiber) {
645 + const ref = finishedWork.ref;
646 + if (ref !== null) {
647 + const instance = finishedWork.stateNode;
648 + let instanceToUse;
649 + switch (finishedWork.tag) {
650 + case HostHoistable:
651 + case HostSingleton:
652 + case HostComponent:
653 + instanceToUse = getPublicInstance(instance);
654 + break;
655 + default:
656 + instanceToUse = instance;
657 + }
658 + // Moved outside to ensure DCE works with this flag
659 + if (enableScopeAPI && finishedWork.tag === ScopeComponent) {
660 + instanceToUse = instance;
661 + }
662 + if (typeof ref === 'function') {
663 + if (shouldProfile(finishedWork)) {
664 + try {
665 + startLayoutEffectTimer();
666 + finishedWork.refCleanup = ref(instanceToUse);
667 + } finally {
668 + recordLayoutEffectDuration(finishedWork);
669 + }
670 + } else {
671 + finishedWork.refCleanup = ref(instanceToUse);
672 + }
673 + } else {
674 + if (__DEV__) {
675 + // TODO: We should move these warnings to happen during the render
676 + // phase (markRef).
677 + if (disableStringRefs && typeof ref === 'string') {
678 + console.error('String refs are no longer supported.');
679 + } else if (!ref.hasOwnProperty('current')) {
680 + console.error(
681 + 'Unexpected ref object provided for %s. ' +
682 + 'Use either a ref-setter function or React.createRef().',
683 + getComponentNameFromFiber(finishedWork),
684 + );
685 + }
686 + }
687 +
688 + // $FlowFixMe[incompatible-use] unable to narrow type to the non-function case
689 + ref.current = instanceToUse;
690 + }
691 + }
692 +}
693 +
694 +// Capture errors so they don't interrupt mounting.
695 +export function safelyAttachRef(
696 + current: Fiber,
697 + nearestMountedAncestor: Fiber | null,
698 +) {
699 + try {
700 + commitAttachRef(current);
701 + } catch (error) {
702 + captureCommitPhaseError(current, nearestMountedAncestor, error);
703 + }
704 +}
705 +
706 +export function safelyDetachRef(
707 + current: Fiber,
708 + nearestMountedAncestor: Fiber | null,
709 +) {
710 + const ref = current.ref;
711 + const refCleanup = current.refCleanup;
712 +
713 + if (ref !== null) {
714 + if (typeof refCleanup === 'function') {
715 + try {
716 + if (shouldProfile(current)) {
717 + try {
718 + startLayoutEffectTimer();
719 + refCleanup();
720 + } finally {
721 + recordLayoutEffectDuration(current);
722 + }
723 + } else {
724 + refCleanup();
725 + }
726 + } catch (error) {
727 + captureCommitPhaseError(current, nearestMountedAncestor, error);
728 + } finally {
729 + // `refCleanup` has been called. Nullify all references to it to prevent double invocation.
730 + current.refCleanup = null;
731 + const finishedWork = current.alternate;
732 + if (finishedWork != null) {
733 + finishedWork.refCleanup = null;
734 + }
735 + }
736 + } else if (typeof ref === 'function') {
737 + try {
738 + if (shouldProfile(current)) {
739 + try {
740 + startLayoutEffectTimer();
741 + ref(null);
742 + } finally {
743 + recordLayoutEffectDuration(current);
744 + }
745 + } else {
746 + ref(null);
747 + }
748 + } catch (error) {
749 + captureCommitPhaseError(current, nearestMountedAncestor, error);
750 + }
751 + } else {
752 + // $FlowFixMe[incompatible-use] unable to narrow type to RefObject
753 + ref.current = null;
754 + }
755 + }
756 +}
757 +
758 +export function safelyCallDestroy(
759 + current: Fiber,
760 + nearestMountedAncestor: Fiber | null,
761 + destroy: () => void,
762 +) {
763 + if (__DEV__) {
764 + callDestroyInDEV(current, nearestMountedAncestor, destroy);
765 + } else {
766 + try {
767 + destroy();
768 + } catch (error) {
769 + captureCommitPhaseError(current, nearestMountedAncestor, error);
770 + }
771 + }
772 +}
773 +
774 +export function commitProfilerUpdate(
775 + finishedWork: Fiber,
776 + current: Fiber | null,
777 + commitTime: number,
778 + effectDuration: number,
779 +) {
780 + if (enableProfilerTimer && getExecutionContext() & CommitContext) {
781 + try {
782 + const {onCommit, onRender} = finishedWork.memoizedProps;
783 +
784 + let phase = current === null ? 'mount' : 'update';
785 + if (enableProfilerNestedUpdatePhase) {
786 + if (isCurrentUpdateNested()) {
787 + phase = 'nested-update';
788 + }
789 + }
790 +
791 + if (typeof onRender === 'function') {
792 + onRender(
793 + finishedWork.memoizedProps.id,
794 + phase,
795 + finishedWork.actualDuration,
796 + finishedWork.treeBaseDuration,
797 + finishedWork.actualStartTime,
798 + commitTime,
799 + );
800 + }
801 +
802 + if (enableProfilerCommitHooks) {
803 + if (typeof onCommit === 'function') {
804 + onCommit(
805 + finishedWork.memoizedProps.id,
806 + phase,
807 + effectDuration,
808 + commitTime,
809 + );
810 + }
811 + }
812 + } catch (error) {
813 + captureCommitPhaseError(finishedWork, finishedWork.return, error);
814 + }
815 + }
816 +}
packages/react-reconciler/src/ReactFiberCommitHostEffects.js new
+430
@@ -0,0 +1,430 @@
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 + Instance,
12 + TextInstance,
13 + SuspenseInstance,
14 + Container,
15 + ChildSet,
16 +} from './ReactFiberConfig';
17 +import type {Fiber, FiberRoot} from './ReactInternalTypes';
18 +
19 +import {
20 + HostRoot,
21 + HostComponent,
22 + HostHoistable,
23 + HostSingleton,
24 + HostText,
25 + HostPortal,
26 + DehydratedFragment,
27 +} from './ReactWorkTags';
28 +import {ContentReset, Placement} from './ReactFiberFlags';
29 +import {
30 + supportsMutation,
31 + supportsResources,
32 + supportsSingletons,
33 + commitMount,
34 + commitUpdate,
35 + resetTextContent,
36 + commitTextUpdate,
37 + appendChild,
38 + appendChildToContainer,
39 + insertBefore,
40 + insertInContainerBefore,
41 + replaceContainerChildren,
42 + hideInstance,
43 + hideTextInstance,
44 + unhideInstance,
45 + unhideTextInstance,
46 + commitHydratedContainer,
47 + commitHydratedSuspenseInstance,
48 + removeChildFromContainer,
49 + removeChild,
50 + clearSingleton,
51 + acquireSingletonInstance,
52 +} from './ReactFiberConfig';
53 +import {captureCommitPhaseError} from './ReactFiberWorkLoop';
54 +
55 +export function commitHostMount(finishedWork: Fiber) {
56 + const type = finishedWork.type;
57 + const props = finishedWork.memoizedProps;
58 + const instance: Instance = finishedWork.stateNode;
59 + try {
60 + commitMount(instance, type, props, finishedWork);
61 + } catch (error) {
62 + captureCommitPhaseError(finishedWork, finishedWork.return, error);
63 + }
64 +}
65 +
66 +export function commitHostUpdate(
67 + finishedWork: Fiber,
68 + newProps: any,
69 + oldProps: any,
70 +) {
71 + try {
72 + commitUpdate(
73 + finishedWork.stateNode,
74 + finishedWork.type,
75 + oldProps,
76 + newProps,
77 + finishedWork,
78 + );
79 + } catch (error) {
80 + captureCommitPhaseError(finishedWork, finishedWork.return, error);
81 + }
82 +}
83 +
84 +export function commitHostTextUpdate(
85 + finishedWork: Fiber,
86 + newText: string,
87 + oldText: string,
88 +) {
89 + const textInstance: TextInstance = finishedWork.stateNode;
90 + try {
91 + commitTextUpdate(textInstance, oldText, newText);
92 + } catch (error) {
93 + captureCommitPhaseError(finishedWork, finishedWork.return, error);
94 + }
95 +}
96 +
97 +export function commitHostResetTextContent(finishedWork: Fiber) {
98 + const instance: Instance = finishedWork.stateNode;
99 + try {
100 + resetTextContent(instance);
101 + } catch (error) {
102 + captureCommitPhaseError(finishedWork, finishedWork.return, error);
103 + }
104 +}
105 +
106 +export function commitShowHideHostInstance(node: Fiber, isHidden: boolean) {
107 + try {
108 + const instance = node.stateNode;
109 + if (isHidden) {
110 + hideInstance(instance);
111 + } else {
112 + unhideInstance(node.stateNode, node.memoizedProps);
113 + }
114 + } catch (error) {
115 + captureCommitPhaseError(node, node.return, error);
116 + }
117 +}
118 +
119 +export function commitShowHideHostTextInstance(node: Fiber, isHidden: boolean) {
120 + try {
121 + const instance = node.stateNode;
122 + if (isHidden) {
123 + hideTextInstance(instance);
124 + } else {
125 + unhideTextInstance(instance, node.memoizedProps);
126 + }
127 + } catch (error) {
128 + captureCommitPhaseError(node, node.return, error);
129 + }
130 +}
131 +
132 +function getHostParentFiber(fiber: Fiber): Fiber {
133 + let parent = fiber.return;
134 + while (parent !== null) {
135 + if (isHostParent(parent)) {
136 + return parent;
137 + }
138 + parent = parent.return;
139 + }
140 +
141 + throw new Error(
142 + 'Expected to find a host parent. This error is likely caused by a bug ' +
143 + 'in React. Please file an issue.',
144 + );
145 +}
146 +
147 +function isHostParent(fiber: Fiber): boolean {
148 + return (
149 + fiber.tag === HostComponent ||
150 + fiber.tag === HostRoot ||
151 + (supportsResources ? fiber.tag === HostHoistable : false) ||
152 + (supportsSingletons ? fiber.tag === HostSingleton : false) ||
153 + fiber.tag === HostPortal
154 + );
155 +}
156 +
157 +function getHostSibling(fiber: Fiber): ?Instance {
158 + // We're going to search forward into the tree until we find a sibling host
159 + // node. Unfortunately, if multiple insertions are done in a row we have to
160 + // search past them. This leads to exponential search for the next sibling.
161 + // TODO: Find a more efficient way to do this.
162 + let node: Fiber = fiber;
163 + siblings: while (true) {
164 + // If we didn't find anything, let's try the next sibling.
165 + while (node.sibling === null) {
166 + if (node.return === null || isHostParent(node.return)) {
167 + // If we pop out of the root or hit the parent the fiber we are the
168 + // last sibling.
169 + return null;
170 + }
171 + // $FlowFixMe[incompatible-type] found when upgrading Flow
172 + node = node.return;
173 + }
174 + node.sibling.return = node.return;
175 + node = node.sibling;
176 + while (
177 + node.tag !== HostComponent &&
178 + node.tag !== HostText &&
179 + (!supportsSingletons ? true : node.tag !== HostSingleton) &&
180 + node.tag !== DehydratedFragment
181 + ) {
182 + // If it is not host node and, we might have a host node inside it.
183 + // Try to search down until we find one.
184 + if (node.flags & Placement) {
185 + // If we don't have a child, try the siblings instead.
186 + continue siblings;
187 + }
188 + // If we don't have a child, try the siblings instead.
189 + // We also skip portals because they are not part of this host tree.
190 + if (node.child === null || node.tag === HostPortal) {
191 + continue siblings;
192 + } else {
193 + node.child.return = node;
194 + node = node.child;
195 + }
196 + }
197 + // Check if this host node is stable or about to be placed.
198 + if (!(node.flags & Placement)) {
199 + // Found it!
200 + return node.stateNode;
201 + }
202 + }
203 +}
204 +
205 +function insertOrAppendPlacementNodeIntoContainer(
206 + node: Fiber,
207 + before: ?Instance,
208 + parent: Container,
209 +): void {
210 + const {tag} = node;
211 + const isHost = tag === HostComponent || tag === HostText;
212 + if (isHost) {
213 + const stateNode = node.stateNode;
214 + if (before) {
215 + insertInContainerBefore(parent, stateNode, before);
216 + } else {
217 + appendChildToContainer(parent, stateNode);
218 + }
219 + } else if (
220 + tag === HostPortal ||
221 + (supportsSingletons ? tag === HostSingleton : false)
222 + ) {
223 + // If the insertion itself is a portal, then we don't want to traverse
224 + // down its children. Instead, we'll get insertions from each child in
225 + // the portal directly.
226 + // If the insertion is a HostSingleton then it will be placed independently
227 + } else {
228 + const child = node.child;
229 + if (child !== null) {
230 + insertOrAppendPlacementNodeIntoContainer(child, before, parent);
231 + let sibling = child.sibling;
232 + while (sibling !== null) {
233 + insertOrAppendPlacementNodeIntoContainer(sibling, before, parent);
234 + sibling = sibling.sibling;
235 + }
236 + }
237 + }
238 +}
239 +
240 +function insertOrAppendPlacementNode(
241 + node: Fiber,
242 + before: ?Instance,
243 + parent: Instance,
244 +): void {
245 + const {tag} = node;
246 + const isHost = tag === HostComponent || tag === HostText;
247 + if (isHost) {
248 + const stateNode = node.stateNode;
249 + if (before) {
250 + insertBefore(parent, stateNode, before);
251 + } else {
252 + appendChild(parent, stateNode);
253 + }
254 + } else if (
255 + tag === HostPortal ||
256 + (supportsSingletons ? tag === HostSingleton : false)
257 + ) {
258 + // If the insertion itself is a portal, then we don't want to traverse
259 + // down its children. Instead, we'll get insertions from each child in
260 + // the portal directly.
261 + // If the insertion is a HostSingleton then it will be placed independently
262 + } else {
263 + const child = node.child;
264 + if (child !== null) {
265 + insertOrAppendPlacementNode(child, before, parent);
266 + let sibling = child.sibling;
267 + while (sibling !== null) {
268 + insertOrAppendPlacementNode(sibling, before, parent);
269 + sibling = sibling.sibling;
270 + }
271 + }
272 + }
273 +}
274 +
275 +function commitPlacement(finishedWork: Fiber): void {
276 + if (!supportsMutation) {
277 + return;
278 + }
279 +
280 + if (supportsSingletons) {
281 + if (finishedWork.tag === HostSingleton) {
282 + // Singletons are already in the Host and don't need to be placed
283 + // Since they operate somewhat like Portals though their children will
284 + // have Placement and will get placed inside them
285 + return;
286 + }
287 + }
288 + // Recursively insert all host nodes into the parent.
289 + const parentFiber = getHostParentFiber(finishedWork);
290 +
291 + switch (parentFiber.tag) {
292 + case HostSingleton: {
293 + if (supportsSingletons) {
294 + const parent: Instance = parentFiber.stateNode;
295 + const before = getHostSibling(finishedWork);
296 + // We only have the top Fiber that was inserted but we need to recurse down its
297 + // children to find all the terminal nodes.
298 + insertOrAppendPlacementNode(finishedWork, before, parent);
299 + break;
300 + }
301 + // Fall through
302 + }
303 + case HostComponent: {
304 + const parent: Instance = parentFiber.stateNode;
305 + if (parentFiber.flags & ContentReset) {
306 + // Reset the text content of the parent before doing any insertions
307 + resetTextContent(parent);
308 + // Clear ContentReset from the effect tag
309 + parentFiber.flags &= ~ContentReset;
310 + }
311 +
312 + const before = getHostSibling(finishedWork);
313 + // We only have the top Fiber that was inserted but we need to recurse down its
314 + // children to find all the terminal nodes.
315 + insertOrAppendPlacementNode(finishedWork, before, parent);
316 + break;
317 + }
318 + case HostRoot:
319 + case HostPortal: {
320 + const parent: Container = parentFiber.stateNode.containerInfo;
321 + const before = getHostSibling(finishedWork);
322 + insertOrAppendPlacementNodeIntoContainer(finishedWork, before, parent);
323 + break;
324 + }
325 + default:
326 + throw new Error(
327 + 'Invalid host parent fiber. This error is likely caused by a bug ' +
328 + 'in React. Please file an issue.',
329 + );
330 + }
331 +}
332 +
333 +export function commitHostPlacement(finishedWork: Fiber) {
334 + try {
335 + commitPlacement(finishedWork);
336 + } catch (error) {
337 + captureCommitPhaseError(finishedWork, finishedWork.return, error);
338 + }
339 +}
340 +
341 +export function commitHostRemoveChildFromContainer(
342 + deletedFiber: Fiber,
343 + nearestMountedAncestor: Fiber,
344 + parentContainer: Container,
345 + hostInstance: Instance | TextInstance,
346 +) {
347 + try {
348 + removeChildFromContainer(parentContainer, hostInstance);
349 + } catch (error) {
350 + captureCommitPhaseError(deletedFiber, nearestMountedAncestor, error);
351 + }
352 +}
353 +
354 +export function commitHostRemoveChild(
355 + deletedFiber: Fiber,
356 + nearestMountedAncestor: Fiber,
357 + parentInstance: Instance,
358 + hostInstance: Instance | TextInstance,
359 +) {
360 + try {
361 + removeChild(parentInstance, hostInstance);
362 + } catch (error) {
363 + captureCommitPhaseError(deletedFiber, nearestMountedAncestor, error);
364 + }
365 +}
366 +
367 +export function commitHostRootContainerChildren(
368 + root: FiberRoot,
369 + finishedWork: Fiber,
370 +) {
371 + const containerInfo = root.containerInfo;
372 + const pendingChildren = root.pendingChildren;
373 + try {
374 + replaceContainerChildren(containerInfo, pendingChildren);
375 + } catch (error) {
376 + captureCommitPhaseError(finishedWork, finishedWork.return, error);
377 + }
378 +}
379 +
380 +export function commitHostPortalContainerChildren(
381 + portal: {
382 + containerInfo: Container,
383 + pendingChildren: ChildSet,
384 + ...
385 + },
386 + finishedWork: Fiber,
387 + pendingChildren: ChildSet,
388 +) {
389 + const containerInfo = portal.containerInfo;
390 + try {
391 + replaceContainerChildren(containerInfo, pendingChildren);
392 + } catch (error) {
393 + captureCommitPhaseError(finishedWork, finishedWork.return, error);
394 + }
395 +}
396 +
397 +export function commitHostHydratedContainer(
398 + root: FiberRoot,
399 + finishedWork: Fiber,
400 +) {
401 + try {
402 + commitHydratedContainer(root.containerInfo);
403 + } catch (error) {
404 + captureCommitPhaseError(finishedWork, finishedWork.return, error);
405 + }
406 +}
407 +
408 +export function commitHostHydratedSuspense(
409 + suspenseInstance: SuspenseInstance,
410 + finishedWork: Fiber,
411 +) {
412 + try {
413 + commitHydratedSuspenseInstance(suspenseInstance);
414 + } catch (error) {
415 + captureCommitPhaseError(finishedWork, finishedWork.return, error);
416 + }
417 +}
418 +
419 +export function commitHostSingleton(finishedWork: Fiber) {
420 + const singleton = finishedWork.stateNode;
421 + const props = finishedWork.memoizedProps;
422 +
423 + try {
424 + // This was a new mount, we need to clear and set initial properties
425 + clearSingleton(singleton);
426 + acquireSingletonInstance(finishedWork.type, props, singleton, finishedWork);
427 + } catch (error) {
428 + captureCommitPhaseError(finishedWork, finishedWork.return, error);
429 + }
430 +}
packages/react-reconciler/src/ReactFiberCommitWork.js
+213 -1220
@@ -12,7 +12,6 @@ import type {
12 TextInstance,
13 SuspenseInstance,
14 Container,
15 - ChildSet,
15 HoistableRoot,
16 FormInstance,
17 } from './ReactFiberConfig';
@@ -30,7 +29,6 @@ import type {
29 OffscreenQueue,
30 OffscreenProps,
31 } from './ReactFiberActivityComponent';
33 -import type {HookFlags} from './ReactHookEffectTags';
32 import type {Cache} from './ReactFiberCacheComponent';
33 import type {RootState} from './ReactFiberRoot';
34 import type {
@@ -54,7 +52,6 @@ import {
52 enableTransitionTracing,
53 enableUseEffectEventHook,
54 enableLegacyHidden,
57 - disableStringRefs,
55 disableLegacyMode,
56 } from 'shared/ReactFeatureFlags';
57 import {
@@ -101,57 +98,29 @@ import {
98 FormReset,
99 Cloned,
100 } from './ReactFiberFlags';
104 -import getComponentNameFromFiber from 'react-reconciler/src/getComponentNameFromFiber';
101 import {runWithFiberInDEV} from './ReactCurrentFiber';
106 -import {resolveClassComponentProps} from './ReactFiberClassComponent';
102 import {
103 isCurrentUpdateNested,
104 getCommitTime,
105 recordLayoutEffectDuration,
106 startLayoutEffectTimer,
112 - recordPassiveEffectDuration,
113 - startPassiveEffectTimer,
107 } from './ReactProfilerTimer';
108 import {ConcurrentMode, NoMode, ProfileMode} from './ReactTypeOfMode';
109 +import {deferHiddenCallbacks} from './ReactFiberClassUpdateQueue';
110 import {
117 - deferHiddenCallbacks,
118 - commitHiddenCallbacks,
119 - commitCallbacks,
120 -} from './ReactFiberClassUpdateQueue';
121 -import {
122 - getPublicInstance,
111 supportsMutation,
112 supportsPersistence,
113 supportsHydration,
114 supportsResources,
115 supportsSingletons,
128 - commitMount,
129 - commitUpdate,
130 - resetTextContent,
131 - commitTextUpdate,
132 - appendChild,
133 - appendChildToContainer,
134 - insertBefore,
135 - insertInContainerBefore,
136 - removeChild,
137 - removeChildFromContainer,
116 clearSuspenseBoundary,
117 clearSuspenseBoundaryFromContainer,
140 - replaceContainerChildren,
118 createContainerChildSet,
142 - hideInstance,
143 - hideTextInstance,
144 - unhideInstance,
145 - unhideTextInstance,
146 - commitHydratedContainer,
147 - commitHydratedSuspenseInstance,
119 clearContainer,
120 prepareScopeUpdate,
121 prepareForCommit,
122 beforeActiveInstanceBlur,
123 detachDeletedInstance,
153 - clearSingleton,
154 - acquireSingletonInstance,
124 releaseSingletonInstance,
125 getHoistableRoot,
126 acquireResource,
@@ -176,7 +145,6 @@ import {
145 addMarkerProgressCallbackToPendingTransition,
146 addMarkerIncompleteCallbackToPendingTransition,
147 addMarkerCompleteCallbackToPendingTransition,
179 - setIsRunningInsertionEffect,
148 getExecutionContext,
149 CommitContext,
150 NoContext,
@@ -188,16 +156,9 @@ import {
156 Insertion as HookInsertion,
157 Passive as HookPassive,
158 } from './ReactHookEffectTags';
191 -import {didWarnAboutReassigningProps} from './ReactFiberBeginWork';
159 import {doesFiberContain} from './ReactFiberTreeReflection';
160 import {
161 isDevToolsPresent,
195 - markComponentPassiveEffectMountStarted,
196 - markComponentPassiveEffectMountStopped,
197 - markComponentPassiveEffectUnmountStarted,
198 - markComponentPassiveEffectUnmountStopped,
199 - markComponentLayoutEffectMountStarted,
200 - markComponentLayoutEffectMountStopped,
162 markComponentLayoutEffectUnmountStarted,
163 markComponentLayoutEffectUnmountStopped,
164 onCommitUnmount,
@@ -216,17 +177,39 @@ import {
177 import {scheduleUpdateOnFiber} from './ReactFiberWorkLoop';
178 import {enqueueConcurrentRenderForLane} from './ReactFiberConcurrentUpdates';
179 import {
219 - callComponentDidMountInDEV,
220 - callComponentDidUpdateInDEV,
221 - callComponentWillUnmountInDEV,
222 - callCreateInDEV,
223 - callDestroyInDEV,
224 -} from './ReactFiberCallUserSpace';
225 -
226 -let didWarnAboutUndefinedSnapshotBeforeUpdate: Set<mixed> | null = null;
227 -if (__DEV__) {
228 - didWarnAboutUndefinedSnapshotBeforeUpdate = new Set();
229 -}
180 + commitHookLayoutEffects,
181 + commitHookEffectListMount,
182 + commitHookEffectListUnmount,
183 + commitHookPassiveMountEffects,
184 + commitHookPassiveUnmountEffects,
185 + commitClassLayoutLifecycles,
186 + commitClassDidMount,
187 + commitClassCallbacks,
188 + commitClassHiddenCallbacks,
189 + commitClassSnapshot,
190 + safelyCallComponentWillUnmount,
191 + safelyAttachRef,
192 + safelyDetachRef,
193 + safelyCallDestroy,
194 + commitProfilerUpdate,
195 + commitRootCallbacks,
196 +} from './ReactFiberCommitEffects';
197 +import {
198 + commitHostMount,
199 + commitHostUpdate,
200 + commitHostTextUpdate,
201 + commitHostResetTextContent,
202 + commitShowHideHostInstance,
203 + commitShowHideHostTextInstance,
204 + commitHostPlacement,
205 + commitHostRootContainerChildren,
206 + commitHostPortalContainerChildren,
207 + commitHostHydratedContainer,
208 + commitHostHydratedSuspense,
209 + commitHostRemoveChildFromContainer,
210 + commitHostRemoveChild,
211 + commitHostSingleton,
212 +} from './ReactFiberCommitHostEffects';
213
214 // Used during the commit phase to track the state of the Offscreen component stack.
215 // Allows us to avoid traversing the return path to find the nearest Offscreen ancestor.
@@ -253,117 +236,6 @@ function shouldProfile(current: Fiber): boolean {
236 );
237 }
238
256 -// Capture errors so they don't interrupt unmounting.
257 -function safelyCallComponentWillUnmount(
258 - current: Fiber,
259 - nearestMountedAncestor: Fiber | null,
260 - instance: any,
261 -) {
262 - instance.props = resolveClassComponentProps(
263 - current.type,
264 - current.memoizedProps,
265 - current.elementType === current.type,
266 - );
267 - instance.state = current.memoizedState;
268 - if (shouldProfile(current)) {
269 - startLayoutEffectTimer();
270 - if (__DEV__) {
271 - callComponentWillUnmountInDEV(current, nearestMountedAncestor, instance);
272 - } else {
273 - try {
274 - instance.componentWillUnmount();
275 - } catch (error) {
276 - captureCommitPhaseError(current, nearestMountedAncestor, error);
277 - }
278 - }
279 - recordLayoutEffectDuration(current);
280 - } else {
281 - if (__DEV__) {
282 - callComponentWillUnmountInDEV(current, nearestMountedAncestor, instance);
283 - } else {
284 - try {
285 - instance.componentWillUnmount();
286 - } catch (error) {
287 - captureCommitPhaseError(current, nearestMountedAncestor, error);
288 - }
289 - }
290 - }
291 -}
292 -
293 -// Capture errors so they don't interrupt mounting.
294 -function safelyAttachRef(current: Fiber, nearestMountedAncestor: Fiber | null) {
295 - try {
296 - commitAttachRef(current);
297 - } catch (error) {
298 - captureCommitPhaseError(current, nearestMountedAncestor, error);
299 - }
300 -}
301 -
302 -function safelyDetachRef(current: Fiber, nearestMountedAncestor: Fiber | null) {
303 - const ref = current.ref;
304 - const refCleanup = current.refCleanup;
305 -
306 - if (ref !== null) {
307 - if (typeof refCleanup === 'function') {
308 - try {
309 - if (shouldProfile(current)) {
310 - try {
311 - startLayoutEffectTimer();
312 - refCleanup();
313 - } finally {
314 - recordLayoutEffectDuration(current);
315 - }
316 - } else {
317 - refCleanup();
318 - }
319 - } catch (error) {
320 - captureCommitPhaseError(current, nearestMountedAncestor, error);
321 - } finally {
322 - // `refCleanup` has been called. Nullify all references to it to prevent double invocation.
323 - current.refCleanup = null;
324 - const finishedWork = current.alternate;
325 - if (finishedWork != null) {
326 - finishedWork.refCleanup = null;
327 - }
328 - }
329 - } else if (typeof ref === 'function') {
330 - try {
331 - if (shouldProfile(current)) {
332 - try {
333 - startLayoutEffectTimer();
334 - ref(null);
335 - } finally {
336 - recordLayoutEffectDuration(current);
337 - }
338 - } else {
339 - ref(null);
340 - }
341 - } catch (error) {
342 - captureCommitPhaseError(current, nearestMountedAncestor, error);
343 - }
344 - } else {
345 - // $FlowFixMe[incompatible-use] unable to narrow type to RefObject
346 - ref.current = null;
347 - }
348 - }
349 -}
350 -
351 -function safelyCallDestroy(
352 - current: Fiber,
353 - nearestMountedAncestor: Fiber | null,
354 - destroy: () => void,
355 -) {
356 - if (__DEV__) {
357 - callDestroyInDEV(current, nearestMountedAncestor, destroy);
358 - } else {
359 - try {
360 - destroy();
361 - } catch (error) {
362 - captureCommitPhaseError(current, nearestMountedAncestor, error);
363 - }
364 - }
365 -}
366 -
239 let focusedInstanceHandle: null | Fiber = null;
240 let shouldFireAfterActiveInstanceBlur: boolean = false;
241
@@ -417,14 +289,10 @@ function commitBeforeMutationEffects_begin() {
289 function commitBeforeMutationEffects_complete() {
290 while (nextEffect !== null) {
291 const fiber = nextEffect;
420 - try {
421 - if (__DEV__) {
422 - runWithFiberInDEV(fiber, commitBeforeMutationEffectsOnFiber, fiber);
423 - } else {
424 - commitBeforeMutationEffectsOnFiber(fiber);
425 - }
426 - } catch (error) {
427 - captureCommitPhaseError(fiber, fiber.return, error);
292 + if (__DEV__) {
293 + runWithFiberInDEV(fiber, commitBeforeMutationEffectsOnFiber, fiber);
294 + } else {
295 + commitBeforeMutationEffectsOnFiber(fiber);
296 }
297
298 const sibling = fiber.sibling;
@@ -462,7 +330,16 @@ function commitBeforeMutationEffectsOnFiber(finishedWork: Fiber) {
330 case FunctionComponent: {
331 if (enableUseEffectEventHook) {
332 if ((flags & Update) !== NoFlags) {
465 - commitUseEffectEventMount(finishedWork);
333 + const updateQueue: FunctionComponentUpdateQueue | null =
334 + (finishedWork.updateQueue: any);
335 + const eventPayloads =
336 + updateQueue !== null ? updateQueue.events : null;
337 + if (eventPayloads !== null) {
338 + for (let ii = 0; ii < eventPayloads.length; ii++) {
339 + const {ref, nextImpl} = eventPayloads[ii];
340 + ref.impl = nextImpl;
341 + }
342 + }
343 }
344 }
345 break;
@@ -474,61 +351,7 @@ function commitBeforeMutationEffectsOnFiber(finishedWork: Fiber) {
351 case ClassComponent: {
352 if ((flags & Snapshot) !== NoFlags) {
353 if (current !== null) {
477 - const prevProps = current.memoizedProps;
478 - const prevState = current.memoizedState;
479 - const instance = finishedWork.stateNode;
480 - // We could update instance props and state here,
481 - // but instead we rely on them being set during last render.
482 - // TODO: revisit this when we implement resuming.
483 - if (__DEV__) {
484 - if (
485 - !finishedWork.type.defaultProps &&
486 - !('ref' in finishedWork.memoizedProps) &&
487 - !didWarnAboutReassigningProps
488 - ) {
489 - if (instance.props !== finishedWork.memoizedProps) {
490 - console.error(
491 - 'Expected %s props to match memoized props before ' +
492 - 'getSnapshotBeforeUpdate. ' +
493 - 'This might either be because of a bug in React, or because ' +
494 - 'a component reassigns its own `this.props`. ' +
495 - 'Please file an issue.',
496 - getComponentNameFromFiber(finishedWork) || 'instance',
497 - );
498 - }
499 - if (instance.state !== finishedWork.memoizedState) {
500 - console.error(
501 - 'Expected %s state to match memoized state before ' +
502 - 'getSnapshotBeforeUpdate. ' +
503 - 'This might either be because of a bug in React, or because ' +
504 - 'a component reassigns its own `this.state`. ' +
505 - 'Please file an issue.',
506 - getComponentNameFromFiber(finishedWork) || 'instance',
507 - );
508 - }
509 - }
510 - }
511 - const snapshot = instance.getSnapshotBeforeUpdate(
512 - resolveClassComponentProps(
513 - finishedWork.type,
514 - prevProps,
515 - finishedWork.elementType === finishedWork.type,
516 - ),
517 - prevState,
518 - );
519 - if (__DEV__) {
520 - const didWarnSet =
521 - ((didWarnAboutUndefinedSnapshotBeforeUpdate: any): Set<mixed>);
522 - if (snapshot === undefined && !didWarnSet.has(finishedWork.type)) {
523 - didWarnSet.add(finishedWork.type);
524 - console.error(
525 - '%s.getSnapshotBeforeUpdate(): A snapshot value (or null) ' +
526 - 'must be returned. You have returned undefined.',
527 - getComponentNameFromFiber(finishedWork),
528 - );
529 - }
530 - }
531 - instance.__reactInternalSnapshotBeforeUpdate = snapshot;
354 + commitClassSnapshot(finishedWork, current);
355 }
356 }
357 break;
@@ -574,161 +397,6 @@ function commitBeforeMutationEffectsDeletion(deletion: Fiber) {
397 }
398 }
399
577 -function commitHookEffectListUnmount(
578 - flags: HookFlags,
579 - finishedWork: Fiber,
580 - nearestMountedAncestor: Fiber | null,
581 -) {
582 - const updateQueue: FunctionComponentUpdateQueue | null =
583 - (finishedWork.updateQueue: any);
584 - const lastEffect = updateQueue !== null ? updateQueue.lastEffect : null;
585 - if (lastEffect !== null) {
586 - const firstEffect = lastEffect.next;
587 - let effect = firstEffect;
588 - do {
589 - if ((effect.tag & flags) === flags) {
590 - // Unmount
591 - const inst = effect.inst;
592 - const destroy = inst.destroy;
593 - if (destroy !== undefined) {
594 - inst.destroy = undefined;
595 - if (enableSchedulingProfiler) {
596 - if ((flags & HookPassive) !== NoHookEffect) {
597 - markComponentPassiveEffectUnmountStarted(finishedWork);
598 - } else if ((flags & HookLayout) !== NoHookEffect) {
599 - markComponentLayoutEffectUnmountStarted(finishedWork);
600 - }
601 - }
602 -
603 - if (__DEV__) {
604 - if ((flags & HookInsertion) !== NoHookEffect) {
605 - setIsRunningInsertionEffect(true);
606 - }
607 - }
608 - safelyCallDestroy(finishedWork, nearestMountedAncestor, destroy);
609 - if (__DEV__) {
610 - if ((flags & HookInsertion) !== NoHookEffect) {
611 - setIsRunningInsertionEffect(false);
612 - }
613 - }
614 -
615 - if (enableSchedulingProfiler) {
616 - if ((flags & HookPassive) !== NoHookEffect) {
617 - markComponentPassiveEffectUnmountStopped();
618 - } else if ((flags & HookLayout) !== NoHookEffect) {
619 - markComponentLayoutEffectUnmountStopped();
620 - }
621 - }
622 - }
623 - }
624 - effect = effect.next;
625 - } while (effect !== firstEffect);
626 - }
627 -}
628 -
629 -function commitHookEffectListMount(flags: HookFlags, finishedWork: Fiber) {
630 - const updateQueue: FunctionComponentUpdateQueue | null =
631 - (finishedWork.updateQueue: any);
632 - const lastEffect = updateQueue !== null ? updateQueue.lastEffect : null;
633 - if (lastEffect !== null) {
634 - const firstEffect = lastEffect.next;
635 - let effect = firstEffect;
636 - do {
637 - if ((effect.tag & flags) === flags) {
638 - if (enableSchedulingProfiler) {
639 - if ((flags & HookPassive) !== NoHookEffect) {
640 - markComponentPassiveEffectMountStarted(finishedWork);
641 - } else if ((flags & HookLayout) !== NoHookEffect) {
642 - markComponentLayoutEffectMountStarted(finishedWork);
643 - }
644 - }
645 -
646 - // Mount
647 - let destroy;
648 - if (__DEV__) {
649 - if ((flags & HookInsertion) !== NoHookEffect) {
650 - setIsRunningInsertionEffect(true);
651 - }
652 - destroy = callCreateInDEV(effect);
653 - if ((flags & HookInsertion) !== NoHookEffect) {
654 - setIsRunningInsertionEffect(false);
655 - }
656 - } else {
657 - const create = effect.create;
658 - const inst = effect.inst;
659 - destroy = create();
660 - inst.destroy = destroy;
661 - }
662 -
663 - if (enableSchedulingProfiler) {
664 - if ((flags & HookPassive) !== NoHookEffect) {
665 - markComponentPassiveEffectMountStopped();
666 - } else if ((flags & HookLayout) !== NoHookEffect) {
667 - markComponentLayoutEffectMountStopped();
668 - }
669 - }
670 -
671 - if (__DEV__) {
672 - if (destroy !== undefined && typeof destroy !== 'function') {
673 - let hookName;
674 - if ((effect.tag & HookLayout) !== NoFlags) {
675 - hookName = 'useLayoutEffect';
676 - } else if ((effect.tag & HookInsertion) !== NoFlags) {
677 - hookName = 'useInsertionEffect';
678 - } else {
679 - hookName = 'useEffect';
680 - }
681 - let addendum;
682 - if (destroy === null) {
683 - addendum =
684 - ' You returned null. If your effect does not require clean ' +
685 - 'up, return undefined (or nothing).';
686 - } else if (typeof destroy.then === 'function') {
687 - addendum =
688 - '\n\nIt looks like you wrote ' +
689 - hookName +
690 - '(async () => ...) or returned a Promise. ' +
691 - 'Instead, write the async function inside your effect ' +
692 - 'and call it immediately:\n\n' +
693 - hookName +
694 - '(() => {\n' +
695 - ' async function fetchData() {\n' +
696 - ' // You can await here\n' +
697 - ' const response = await MyAPI.getData(someId);\n' +
698 - ' // ...\n' +
699 - ' }\n' +
700 - ' fetchData();\n' +
701 - `}, [someId]); // Or [] if effect doesn't need props or state\n\n` +
702 - 'Learn more about data fetching with Hooks: https://react.dev/link/hooks-data-fetching';
703 - } else {
704 - addendum = ' You returned: ' + destroy;
705 - }
706 - console.error(
707 - '%s must not return anything besides a function, ' +
708 - 'which is used for clean-up.%s',
709 - hookName,
710 - addendum,
711 - );
712 - }
713 - }
714 - }
715 - effect = effect.next;
716 - } while (effect !== firstEffect);
717 - }
718 -}
719 -
720 -function commitUseEffectEventMount(finishedWork: Fiber) {
721 - const updateQueue: FunctionComponentUpdateQueue | null =
722 - (finishedWork.updateQueue: any);
723 - const eventPayloads = updateQueue !== null ? updateQueue.events : null;
724 - if (eventPayloads !== null) {
725 - for (let ii = 0; ii < eventPayloads.length; ii++) {
726 - const {ref, nextImpl} = eventPayloads[ii];
727 - ref.impl = nextImpl;
728 - }
729 - }
730 -}
731 -
400 export function commitPassiveEffectDurations(
401 finishedRoot: FiberRoot,
402 finishedWork: Fiber,
@@ -785,293 +453,6 @@ export function commitPassiveEffectDurations(
453 }
454 }
455
788 -function commitHookLayoutEffects(finishedWork: Fiber, hookFlags: HookFlags) {
789 - // At this point layout effects have already been destroyed (during mutation phase).
790 - // This is done to prevent sibling component effects from interfering with each other,
791 - // e.g. a destroy function in one component should never override a ref set
792 - // by a create function in another component during the same commit.
793 - if (shouldProfile(finishedWork)) {
794 - try {
795 - startLayoutEffectTimer();
796 - commitHookEffectListMount(hookFlags, finishedWork);
797 - } catch (error) {
798 - captureCommitPhaseError(finishedWork, finishedWork.return, error);
799 - }
800 - recordLayoutEffectDuration(finishedWork);
801 - } else {
802 - try {
803 - commitHookEffectListMount(hookFlags, finishedWork);
804 - } catch (error) {
805 - captureCommitPhaseError(finishedWork, finishedWork.return, error);
806 - }
807 - }
808 -}
809 -
810 -function commitClassLayoutLifecycles(
811 - finishedWork: Fiber,
812 - current: Fiber | null,
813 -) {
814 - const instance = finishedWork.stateNode;
815 - if (current === null) {
816 - // We could update instance props and state here,
817 - // but instead we rely on them being set during last render.
818 - // TODO: revisit this when we implement resuming.
819 - if (__DEV__) {
820 - if (
821 - !finishedWork.type.defaultProps &&
822 - !('ref' in finishedWork.memoizedProps) &&
823 - !didWarnAboutReassigningProps
824 - ) {
825 - if (instance.props !== finishedWork.memoizedProps) {
826 - console.error(
827 - 'Expected %s props to match memoized props before ' +
828 - 'componentDidMount. ' +
829 - 'This might either be because of a bug in React, or because ' +
830 - 'a component reassigns its own `this.props`. ' +
831 - 'Please file an issue.',
832 - getComponentNameFromFiber(finishedWork) || 'instance',
833 - );
834 - }
835 - if (instance.state !== finishedWork.memoizedState) {
836 - console.error(
837 - 'Expected %s state to match memoized state before ' +
838 - 'componentDidMount. ' +
839 - 'This might either be because of a bug in React, or because ' +
840 - 'a component reassigns its own `this.state`. ' +
841 - 'Please file an issue.',
842 - getComponentNameFromFiber(finishedWork) || 'instance',
843 - );
844 - }
845 - }
846 - }
847 - if (shouldProfile(finishedWork)) {
848 - startLayoutEffectTimer();
849 - if (__DEV__) {
850 - callComponentDidMountInDEV(finishedWork, instance);
851 - } else {
852 - try {
853 - instance.componentDidMount();
854 - } catch (error) {
855 - captureCommitPhaseError(finishedWork, finishedWork.return, error);
856 - }
857 - }
858 - recordLayoutEffectDuration(finishedWork);
859 - } else {
860 - if (__DEV__) {
861 - callComponentDidMountInDEV(finishedWork, instance);
862 - } else {
863 - try {
864 - instance.componentDidMount();
865 - } catch (error) {
866 - captureCommitPhaseError(finishedWork, finishedWork.return, error);
867 - }
868 - }
869 - }
870 - } else {
871 - const prevProps = resolveClassComponentProps(
872 - finishedWork.type,
873 - current.memoizedProps,
874 - finishedWork.elementType === finishedWork.type,
875 - );
876 - const prevState = current.memoizedState;
877 - // We could update instance props and state here,
878 - // but instead we rely on them being set during last render.
879 - // TODO: revisit this when we implement resuming.
880 - if (__DEV__) {
881 - if (
882 - !finishedWork.type.defaultProps &&
883 - !('ref' in finishedWork.memoizedProps) &&
884 - !didWarnAboutReassigningProps
885 - ) {
886 - if (instance.props !== finishedWork.memoizedProps) {
887 - console.error(
888 - 'Expected %s props to match memoized props before ' +
889 - 'componentDidUpdate. ' +
890 - 'This might either be because of a bug in React, or because ' +
891 - 'a component reassigns its own `this.props`. ' +
892 - 'Please file an issue.',
893 - getComponentNameFromFiber(finishedWork) || 'instance',
894 - );
895 - }
896 - if (instance.state !== finishedWork.memoizedState) {
897 - console.error(
898 - 'Expected %s state to match memoized state before ' +
899 - 'componentDidUpdate. ' +
900 - 'This might either be because of a bug in React, or because ' +
901 - 'a component reassigns its own `this.state`. ' +
902 - 'Please file an issue.',
903 - getComponentNameFromFiber(finishedWork) || 'instance',
904 - );
905 - }
906 - }
907 - }
908 - if (shouldProfile(finishedWork)) {
909 - startLayoutEffectTimer();
910 - if (__DEV__) {
911 - callComponentDidUpdateInDEV(
912 - finishedWork,
913 - instance,
914 - prevProps,
915 - prevState,
916 - instance.__reactInternalSnapshotBeforeUpdate,
917 - );
918 - } else {
919 - try {
920 - instance.componentDidUpdate(
921 - prevProps,
922 - prevState,
923 - instance.__reactInternalSnapshotBeforeUpdate,
924 - );
925 - } catch (error) {
926 - captureCommitPhaseError(finishedWork, finishedWork.return, error);
927 - }
928 - }
929 - recordLayoutEffectDuration(finishedWork);
930 - } else {
931 - if (__DEV__) {
932 - callComponentDidUpdateInDEV(
933 - finishedWork,
934 - instance,
935 - prevProps,
936 - prevState,
937 - instance.__reactInternalSnapshotBeforeUpdate,
938 - );
939 - } else {
940 - try {
941 - instance.componentDidUpdate(
942 - prevProps,
943 - prevState,
944 - instance.__reactInternalSnapshotBeforeUpdate,
945 - );
946 - } catch (error) {
947 - captureCommitPhaseError(finishedWork, finishedWork.return, error);
948 - }
949 - }
950 - }
951 - }
952 -}
953 -
954 -function commitClassCallbacks(finishedWork: Fiber) {
955 - // TODO: I think this is now always non-null by the time it reaches the
956 - // commit phase. Consider removing the type check.
957 - const updateQueue: UpdateQueue<mixed> | null =
958 - (finishedWork.updateQueue: any);
959 - if (updateQueue !== null) {
960 - const instance = finishedWork.stateNode;
961 - if (__DEV__) {
962 - if (
963 - !finishedWork.type.defaultProps &&
964 - !('ref' in finishedWork.memoizedProps) &&
965 - !didWarnAboutReassigningProps
966 - ) {
967 - if (instance.props !== finishedWork.memoizedProps) {
968 - console.error(
969 - 'Expected %s props to match memoized props before ' +
970 - 'processing the update queue. ' +
971 - 'This might either be because of a bug in React, or because ' +
972 - 'a component reassigns its own `this.props`. ' +
973 - 'Please file an issue.',
974 - getComponentNameFromFiber(finishedWork) || 'instance',
975 - );
976 - }
977 - if (instance.state !== finishedWork.memoizedState) {
978 - console.error(
979 - 'Expected %s state to match memoized state before ' +
980 - 'processing the update queue. ' +
981 - 'This might either be because of a bug in React, or because ' +
982 - 'a component reassigns its own `this.state`. ' +
983 - 'Please file an issue.',
984 - getComponentNameFromFiber(finishedWork) || 'instance',
985 - );
986 - }
987 - }
988 - }
989 - // We could update instance props and state here,
990 - // but instead we rely on them being set during last render.
991 - // TODO: revisit this when we implement resuming.
992 - try {
993 - commitCallbacks(updateQueue, instance);
994 - } catch (error) {
995 - captureCommitPhaseError(finishedWork, finishedWork.return, error);
996 - }
997 - }
998 -}
999 -
1000 -function commitHostComponentMount(finishedWork: Fiber) {
1001 - const type = finishedWork.type;
1002 - const props = finishedWork.memoizedProps;
1003 - const instance: Instance = finishedWork.stateNode;
1004 - try {
1005 - commitMount(instance, type, props, finishedWork);
1006 - } catch (error) {
1007 - captureCommitPhaseError(finishedWork, finishedWork.return, error);
1008 - }
1009 -}
1010 -
1011 -function commitProfilerUpdate(finishedWork: Fiber, current: Fiber | null) {
1012 - if (enableProfilerTimer && getExecutionContext() & CommitContext) {
1013 - try {
1014 - const {onCommit, onRender} = finishedWork.memoizedProps;
1015 - const {effectDuration} = finishedWork.stateNode;
1016 -
1017 - const commitTime = getCommitTime();
1018 -
1019 - let phase = current === null ? 'mount' : 'update';
1020 - if (enableProfilerNestedUpdatePhase) {
1021 - if (isCurrentUpdateNested()) {
1022 - phase = 'nested-update';
1023 - }
1024 - }
1025 -
1026 - if (typeof onRender === 'function') {
1027 - onRender(
1028 - finishedWork.memoizedProps.id,
1029 - phase,
1030 - finishedWork.actualDuration,
1031 - finishedWork.treeBaseDuration,
1032 - finishedWork.actualStartTime,
1033 - commitTime,
1034 - );
1035 - }
1036 -
1037 - if (enableProfilerCommitHooks) {
1038 - if (typeof onCommit === 'function') {
1039 - onCommit(
1040 - finishedWork.memoizedProps.id,
1041 - phase,
1042 - effectDuration,
1043 - commitTime,
1044 - );
1045 - }
1046 -
1047 - // Schedule a passive effect for this Profiler to call onPostCommit hooks.
1048 - // This effect should be scheduled even if there is no onPostCommit callback for this Profiler,
1049 - // because the effect is also where times bubble to parent Profilers.
1050 - enqueuePendingPassiveProfilerEffect(finishedWork);
1051 -
1052 - // Propagate layout effect durations to the next nearest Profiler ancestor.
1053 - // Do not reset these values until the next render so DevTools has a chance to read them first.
1054 - let parentFiber = finishedWork.return;
1055 - outer: while (parentFiber !== null) {
1056 - switch (parentFiber.tag) {
1057 - case HostRoot:
1058 - const root = parentFiber.stateNode;
1059 - root.effectDuration += effectDuration;
1060 - break outer;
1061 - case Profiler:
1062 - const parentStateNode = parentFiber.stateNode;
1063 - parentStateNode.effectDuration += effectDuration;
1064 - break outer;
1065 - }
1066 - parentFiber = parentFiber.return;
1067 - }
1068 - }
1069 - } catch (error) {
1070 - captureCommitPhaseError(finishedWork, finishedWork.return, error);
1071 - }
1072 - }
1073 -}
1074 -
456 function commitLayoutEffectOnFiber(
457 finishedRoot: FiberRoot,
458 current: Fiber | null,
@@ -1121,29 +502,7 @@ function commitLayoutEffectOnFiber(
502 committedLanes,
503 );
504 if (flags & Callback) {
1124 - // TODO: I think this is now always non-null by the time it reaches the
1125 - // commit phase. Consider removing the type check.
1126 - const updateQueue: UpdateQueue<mixed> | null =
1127 - (finishedWork.updateQueue: any);
1128 - if (updateQueue !== null) {
1129 - let instance = null;
1130 - if (finishedWork.child !== null) {
1131 - switch (finishedWork.child.tag) {
1132 - case HostSingleton:
1133 - case HostComponent:
1134 - instance = getPublicInstance(finishedWork.child.stateNode);
1135 - break;
1136 - case ClassComponent:
1137 - instance = finishedWork.child.stateNode;
1138 - break;
1139 - }
1140 - }
1141 - try {
1142 - commitCallbacks(updateQueue, instance);
1143 - } catch (error) {
1144 - captureCommitPhaseError(finishedWork, finishedWork.return, error);
1145 - }
1146 - }
505 + commitRootCallbacks(finishedWork);
506 }
507 break;
508 }
@@ -1175,7 +534,7 @@ function commitLayoutEffectOnFiber(
534 // These effects should only be committed when components are first mounted,
535 // aka when there is no current/alternate.
536 if (current === null && flags & Update) {
1178 - commitHostComponentMount(finishedWork);
537 + commitHostMount(finishedWork);
538 }
539
540 if (flags & Ref) {
@@ -1192,7 +551,36 @@ function commitLayoutEffectOnFiber(
551 // TODO: Should this fire inside an offscreen tree? Or should it wait to
552 // fire when the tree becomes visible again.
553 if (flags & Update) {
1195 - commitProfilerUpdate(finishedWork, current);
554 + const {effectDuration} = finishedWork.stateNode;
555 +
556 + commitProfilerUpdate(
557 + finishedWork,
558 + current,
559 + getCommitTime(),
560 + effectDuration,
561 + );
562 +
563 + // Schedule a passive effect for this Profiler to call onPostCommit hooks.
564 + // This effect should be scheduled even if there is no onPostCommit callback for this Profiler,
565 + // because the effect is also where times bubble to parent Profilers.
566 + enqueuePendingPassiveProfilerEffect(finishedWork);
567 +
568 + // Propagate layout effect durations to the next nearest Profiler ancestor.
569 + // Do not reset these values until the next render so DevTools has a chance to read them first.
570 + let parentFiber = finishedWork.return;
571 + outer: while (parentFiber !== null) {
572 + switch (parentFiber.tag) {
573 + case HostRoot:
574 + const root = parentFiber.stateNode;
575 + root.effectDuration += effectDuration;
576 + break outer;
577 + case Profiler:
578 + const parentStateNode = parentFiber.stateNode;
579 + parentStateNode.effectDuration += effectDuration;
580 + break outer;
581 + }
582 + parentFiber = parentFiber.return;
583 + }
584 }
585 break;
586 }
@@ -1558,29 +946,11 @@ function hideOrUnhideAllChildren(finishedWork: Fiber, isHidden: boolean) {
946 ) {
947 if (hostSubtreeRoot === null) {
948 hostSubtreeRoot = node;
1561 - try {
1562 - const instance = node.stateNode;
1563 - if (isHidden) {
1564 - hideInstance(instance);
1565 - } else {
1566 - unhideInstance(node.stateNode, node.memoizedProps);
1567 - }
1568 - } catch (error) {
1569 - captureCommitPhaseError(finishedWork, finishedWork.return, error);
1570 - }
949 + commitShowHideHostInstance(node, isHidden);
950 }
951 } else if (node.tag === HostText) {
952 if (hostSubtreeRoot === null) {
1574 - try {
1575 - const instance = node.stateNode;
1576 - if (isHidden) {
1577 - hideTextInstance(instance);
1578 - } else {
1579 - unhideTextInstance(instance, node.memoizedProps);
1580 - }
1581 - } catch (error) {
1582 - captureCommitPhaseError(finishedWork, finishedWork.return, error);
1583 - }
953 + commitShowHideHostTextInstance(node, isHidden);
954 }
955 } else if (
956 (node.tag === OffscreenComponent ||
@@ -1604,69 +974,19 @@ function hideOrUnhideAllChildren(finishedWork: Fiber, isHidden: boolean) {
974 return;
975 }
976
1607 - if (hostSubtreeRoot === node) {
1608 - hostSubtreeRoot = null;
1609 - }
1610 -
1611 - node = node.return;
1612 - }
1613 -
1614 - if (hostSubtreeRoot === node) {
1615 - hostSubtreeRoot = null;
1616 - }
1617 -
1618 - node.sibling.return = node.return;
1619 - node = node.sibling;
1620 - }
1621 - }
1622 -}
1623 -
1624 -function commitAttachRef(finishedWork: Fiber) {
1625 - const ref = finishedWork.ref;
1626 - if (ref !== null) {
1627 - const instance = finishedWork.stateNode;
1628 - let instanceToUse;
1629 - switch (finishedWork.tag) {
1630 - case HostHoistable:
1631 - case HostSingleton:
1632 - case HostComponent:
1633 - instanceToUse = getPublicInstance(instance);
1634 - break;
1635 - default:
1636 - instanceToUse = instance;
1637 - }
1638 - // Moved outside to ensure DCE works with this flag
1639 - if (enableScopeAPI && finishedWork.tag === ScopeComponent) {
1640 - instanceToUse = instance;
1641 - }
1642 - if (typeof ref === 'function') {
1643 - if (shouldProfile(finishedWork)) {
1644 - try {
1645 - startLayoutEffectTimer();
1646 - finishedWork.refCleanup = ref(instanceToUse);
1647 - } finally {
1648 - recordLayoutEffectDuration(finishedWork);
1649 - }
1650 - } else {
1651 - finishedWork.refCleanup = ref(instanceToUse);
1652 - }
1653 - } else {
1654 - if (__DEV__) {
1655 - // TODO: We should move these warnings to happen during the render
1656 - // phase (markRef).
1657 - if (disableStringRefs && typeof ref === 'string') {
1658 - console.error('String refs are no longer supported.');
1659 - } else if (!ref.hasOwnProperty('current')) {
1660 - console.error(
1661 - 'Unexpected ref object provided for %s. ' +
1662 - 'Use either a ref-setter function or React.createRef().',
1663 - getComponentNameFromFiber(finishedWork),
1664 - );
977 + if (hostSubtreeRoot === node) {
978 + hostSubtreeRoot = null;
979 }
980 +
981 + node = node.return;
982 + }
983 +
984 + if (hostSubtreeRoot === node) {
985 + hostSubtreeRoot = null;
986 }
987
1668 - // $FlowFixMe[incompatible-use] unable to narrow type to the non-function case
1669 - ref.current = instanceToUse;
988 + node.sibling.return = node.return;
989 + node = node.sibling;
990 }
991 }
992 }
@@ -1741,222 +1061,6 @@ function detachFiberAfterEffects(fiber: Fiber) {
1061 fiber.updateQueue = null;
1062 }
1063
1744 -function emptyPortalContainer(current: Fiber) {
1745 - if (!supportsPersistence) {
1746 - return;
1747 - }
1748 -
1749 - const portal: {
1750 - containerInfo: Container,
1751 - pendingChildren: ChildSet,
1752 - ...
1753 - } = current.stateNode;
1754 - const {containerInfo} = portal;
1755 - const emptyChildSet = createContainerChildSet();
1756 - replaceContainerChildren(containerInfo, emptyChildSet);
1757 -}
1758 -
1759 -function getHostParentFiber(fiber: Fiber): Fiber {
1760 - let parent = fiber.return;
1761 - while (parent !== null) {
1762 - if (isHostParent(parent)) {
1763 - return parent;
1764 - }
1765 - parent = parent.return;
1766 - }
1767 -
1768 - throw new Error(
1769 - 'Expected to find a host parent. This error is likely caused by a bug ' +
1770 - 'in React. Please file an issue.',
1771 - );
1772 -}
1773 -
1774 -function isHostParent(fiber: Fiber): boolean {
1775 - return (
1776 - fiber.tag === HostComponent ||
1777 - fiber.tag === HostRoot ||
1778 - (supportsResources ? fiber.tag === HostHoistable : false) ||
1779 - (supportsSingletons ? fiber.tag === HostSingleton : false) ||
1780 - fiber.tag === HostPortal
1781 - );
1782 -}
1783 -
1784 -function getHostSibling(fiber: Fiber): ?Instance {
1785 - // We're going to search forward into the tree until we find a sibling host
1786 - // node. Unfortunately, if multiple insertions are done in a row we have to
1787 - // search past them. This leads to exponential search for the next sibling.
1788 - // TODO: Find a more efficient way to do this.
1789 - let node: Fiber = fiber;
1790 - siblings: while (true) {
1791 - // If we didn't find anything, let's try the next sibling.
1792 - while (node.sibling === null) {
1793 - if (node.return === null || isHostParent(node.return)) {
1794 - // If we pop out of the root or hit the parent the fiber we are the
1795 - // last sibling.
1796 - return null;
1797 - }
1798 - // $FlowFixMe[incompatible-type] found when upgrading Flow
1799 - node = node.return;
1800 - }
1801 - node.sibling.return = node.return;
1802 - node = node.sibling;
1803 - while (
1804 - node.tag !== HostComponent &&
1805 - node.tag !== HostText &&
1806 - (!supportsSingletons ? true : node.tag !== HostSingleton) &&
1807 - node.tag !== DehydratedFragment
1808 - ) {
1809 - // If it is not host node and, we might have a host node inside it.
1810 - // Try to search down until we find one.
1811 - if (node.flags & Placement) {
1812 - // If we don't have a child, try the siblings instead.
1813 - continue siblings;
1814 - }
1815 - // If we don't have a child, try the siblings instead.
1816 - // We also skip portals because they are not part of this host tree.
1817 - if (node.child === null || node.tag === HostPortal) {
1818 - continue siblings;
1819 - } else {
1820 - node.child.return = node;
1821 - node = node.child;
1822 - }
1823 - }
1824 - // Check if this host node is stable or about to be placed.
1825 - if (!(node.flags & Placement)) {
1826 - // Found it!
1827 - return node.stateNode;
1828 - }
1829 - }
1830 -}
1831 -
1832 -function commitPlacement(finishedWork: Fiber): void {
1833 - if (!supportsMutation) {
1834 - return;
1835 - }
1836 -
1837 - if (supportsSingletons) {
1838 - if (finishedWork.tag === HostSingleton) {
1839 - // Singletons are already in the Host and don't need to be placed
1840 - // Since they operate somewhat like Portals though their children will
1841 - // have Placement and will get placed inside them
1842 - return;
1843 - }
1844 - }
1845 - // Recursively insert all host nodes into the parent.
1846 - const parentFiber = getHostParentFiber(finishedWork);
1847 -
1848 - switch (parentFiber.tag) {
1849 - case HostSingleton: {
1850 - if (supportsSingletons) {
1851 - const parent: Instance = parentFiber.stateNode;
1852 - const before = getHostSibling(finishedWork);
1853 - // We only have the top Fiber that was inserted but we need to recurse down its
1854 - // children to find all the terminal nodes.
1855 - insertOrAppendPlacementNode(finishedWork, before, parent);
1856 - break;
1857 - }
1858 - // Fall through
1859 - }
1860 - case HostComponent: {
1861 - const parent: Instance = parentFiber.stateNode;
1862 - if (parentFiber.flags & ContentReset) {
1863 - // Reset the text content of the parent before doing any insertions
1864 - resetTextContent(parent);
1865 - // Clear ContentReset from the effect tag
1866 - parentFiber.flags &= ~ContentReset;
1867 - }
1868 -
1869 - const before = getHostSibling(finishedWork);
1870 - // We only have the top Fiber that was inserted but we need to recurse down its
1871 - // children to find all the terminal nodes.
1872 - insertOrAppendPlacementNode(finishedWork, before, parent);
1873 - break;
1874 - }
1875 - case HostRoot:
1876 - case HostPortal: {
1877 - const parent: Container = parentFiber.stateNode.containerInfo;
1878 - const before = getHostSibling(finishedWork);
1879 - insertOrAppendPlacementNodeIntoContainer(finishedWork, before, parent);
1880 - break;
1881 - }
1882 - default:
1883 - throw new Error(
1884 - 'Invalid host parent fiber. This error is likely caused by a bug ' +
1885 - 'in React. Please file an issue.',
1886 - );
1887 - }
1888 -}
1889 -
1890 -function insertOrAppendPlacementNodeIntoContainer(
1891 - node: Fiber,
1892 - before: ?Instance,
1893 - parent: Container,
1894 -): void {
1895 - const {tag} = node;
1896 - const isHost = tag === HostComponent || tag === HostText;
1897 - if (isHost) {
1898 - const stateNode = node.stateNode;
1899 - if (before) {
1900 - insertInContainerBefore(parent, stateNode, before);
1901 - } else {
1902 - appendChildToContainer(parent, stateNode);
1903 - }
1904 - } else if (
1905 - tag === HostPortal ||
1906 - (supportsSingletons ? tag === HostSingleton : false)
1907 - ) {
1908 - // If the insertion itself is a portal, then we don't want to traverse
1909 - // down its children. Instead, we'll get insertions from each child in
1910 - // the portal directly.
1911 - // If the insertion is a HostSingleton then it will be placed independently
1912 - } else {
1913 - const child = node.child;
1914 - if (child !== null) {
1915 - insertOrAppendPlacementNodeIntoContainer(child, before, parent);
1916 - let sibling = child.sibling;
1917 - while (sibling !== null) {
1918 - insertOrAppendPlacementNodeIntoContainer(sibling, before, parent);
1919 - sibling = sibling.sibling;
1920 - }
1921 - }
1922 - }
1923 -}
1924 -
1925 -function insertOrAppendPlacementNode(
1926 - node: Fiber,
1927 - before: ?Instance,
1928 - parent: Instance,
1929 -): void {
1930 - const {tag} = node;
1931 - const isHost = tag === HostComponent || tag === HostText;
1932 - if (isHost) {
1933 - const stateNode = node.stateNode;
1934 - if (before) {
1935 - insertBefore(parent, stateNode, before);
1936 - } else {
1937 - appendChild(parent, stateNode);
1938 - }
1939 - } else if (
1940 - tag === HostPortal ||
1941 - (supportsSingletons ? tag === HostSingleton : false)
1942 - ) {
1943 - // If the insertion itself is a portal, then we don't want to traverse
1944 - // down its children. Instead, we'll get insertions from each child in
1945 - // the portal directly.
1946 - // If the insertion is a HostSingleton then it will be placed independently
1947 - } else {
1948 - const child = node.child;
1949 - if (child !== null) {
1950 - insertOrAppendPlacementNode(child, before, parent);
1951 - let sibling = child.sibling;
1952 - while (sibling !== null) {
1953 - insertOrAppendPlacementNode(sibling, before, parent);
1954 - sibling = sibling.sibling;
1955 - }
1956 - }
1957 - }
1958 -}
1959 -
1064 // These are tracked on the stack as we recursively traverse a
1065 // deleted subtree.
1066 // TODO: Update these during the whole mutation phase, not just during
@@ -2046,6 +1150,7 @@ function commitDeletionEffectsOnFiber(
1150 nearestMountedAncestor: Fiber,
1151 deletedFiber: Fiber,
1152 ) {
1153 + // TODO: Delete this Hook once new DevTools ships everywhere. No longer needed.
1154 onCommitUnmount(deletedFiber);
1155
1156 // The cases in this outer switch modify the stack before they traverse
@@ -2126,12 +1231,16 @@ function commitDeletionEffectsOnFiber(
1231 // Now that all the child effects have unmounted, we can remove the
1232 // node from the tree.
1233 if (hostParentIsContainer) {
2129 - removeChildFromContainer(
1234 + commitHostRemoveChildFromContainer(
1235 + deletedFiber,
1236 + nearestMountedAncestor,
1237 ((hostParent: any): Container),
1238 (deletedFiber.stateNode: Instance | TextInstance),
1239 );
1240 } else {
2134 - removeChild(
1241 + commitHostRemoveChild(
1242 + deletedFiber,
1243 + nearestMountedAncestor,
1244 ((hostParent: any): Instance),
1245 (deletedFiber.stateNode: Instance | TextInstance),
1246 );
@@ -2150,9 +1259,17 @@ function commitDeletionEffectsOnFiber(
1259 if (enableSuspenseCallback) {
1260 const hydrationCallbacks = finishedRoot.hydrationCallbacks;
1261 if (hydrationCallbacks !== null) {
2153 - const onDeleted = hydrationCallbacks.onDeleted;
2154 - if (onDeleted) {
2155 - onDeleted((deletedFiber.stateNode: SuspenseInstance));
1262 + try {
1263 + const onDeleted = hydrationCallbacks.onDeleted;
1264 + if (onDeleted) {
1265 + onDeleted((deletedFiber.stateNode: SuspenseInstance));
1266 + }
1267 + } catch (error) {
1268 + captureCommitPhaseError(
1269 + deletedFiber,
1270 + nearestMountedAncestor,
1271 + error,
1272 + );
1273 }
1274 }
1275 }
@@ -2192,7 +1309,13 @@ function commitDeletionEffectsOnFiber(
1309 hostParent = prevHostParent;
1310 hostParentIsContainer = prevHostParentIsContainer;
1311 } else {
2195 - emptyPortalContainer(deletedFiber);
1312 + if (supportsPersistence) {
1313 + commitHostPortalContainerChildren(
1314 + deletedFiber.stateNode,
1315 + deletedFiber,
1316 + createContainerChildSet(),
1317 + );
1318 + }
1319
1320 recursivelyTraverseDeletionEffects(
1321 finishedRoot,
@@ -2340,7 +1463,7 @@ function commitDeletionEffectsOnFiber(
1463 }
1464 }
1465 function commitSuspenseCallback(finishedWork: Fiber) {
2343 - // TODO: Move this to passive phase
1466 + // TODO: Delete this feature. It's not properly covered by DEV features.
1467 const newState: SuspenseState | null = finishedWork.memoizedState;
1468 if (enableSuspenseCallback && newState !== null) {
1469 const suspenseCallback = finishedWork.memoizedProps.suspenseCallback;
@@ -2372,9 +1495,10 @@ function commitSuspenseHydrationCallbacks(
1495 if (prevState !== null) {
1496 const suspenseInstance = prevState.dehydrated;
1497 if (suspenseInstance !== null) {
2375 - try {
2376 - commitHydratedSuspenseInstance(suspenseInstance);
2377 - if (enableSuspenseCallback) {
1498 + commitHostHydratedSuspense(suspenseInstance, finishedWork);
1499 + if (enableSuspenseCallback) {
1500 + try {
1501 + // TODO: Delete this feature. It's not properly covered by DEV features.
1502 const hydrationCallbacks = finishedRoot.hydrationCallbacks;
1503 if (hydrationCallbacks !== null) {
1504 const onHydrated = hydrationCallbacks.onHydrated;
@@ -2382,9 +1506,9 @@ function commitSuspenseHydrationCallbacks(
1506 onHydrated(suspenseInstance);
1507 }
1508 }
1509 + } catch (error) {
1510 + captureCommitPhaseError(finishedWork, finishedWork.return, error);
1511 }
2386 - } catch (error) {
2387 - captureCommitPhaseError(finishedWork, finishedWork.return, error);
1512 }
1513 }
1514 }
@@ -2499,7 +1623,7 @@ function attachSuspenseRetryListeners(
1623 // This function detects when a Suspense boundary goes from visible to hidden.
1624 // It returns false if the boundary is already hidden.
1625 // TODO: Use an effect tag.
2502 -export function isSuspenseBoundaryBeingHidden(
1626 +function isSuspenseBoundaryBeingHidden(
1627 current: Fiber | null,
1628 finishedWork: Fiber,
1629 ): boolean {
@@ -2548,11 +1672,7 @@ function recursivelyTraverseMutationEffects(
1672 if (deletions !== null) {
1673 for (let i = 0; i < deletions.length; i++) {
1674 const childToDelete = deletions[i];
2551 - try {
2552 - commitDeletionEffects(root, parentFiber, childToDelete);
2553 - } catch (error) {
2554 - captureCommitPhaseError(childToDelete, parentFiber, error);
2555 - }
1675 + commitDeletionEffects(root, parentFiber, childToDelete);
1676 }
1677 }
1678
@@ -2600,46 +1720,31 @@ function commitMutationEffectsOnFiber(
1720 commitReconciliationEffects(finishedWork);
1721
1722 if (flags & Update) {
2603 - try {
2604 - commitHookEffectListUnmount(
2605 - HookInsertion | HookHasEffect,
2606 - finishedWork,
2607 - finishedWork.return,
2608 - );
2609 - commitHookEffectListMount(
2610 - HookInsertion | HookHasEffect,
2611 - finishedWork,
2612 - );
2613 - } catch (error) {
2614 - captureCommitPhaseError(finishedWork, finishedWork.return, error);
2615 - }
1723 + commitHookEffectListUnmount(
1724 + HookInsertion | HookHasEffect,
1725 + finishedWork,
1726 + finishedWork.return,
1727 + );
1728 + commitHookEffectListMount(HookInsertion | HookHasEffect, finishedWork);
1729 // Layout effects are destroyed during the mutation phase so that all
1730 // destroy functions for all fibers are called before any create functions.
1731 // This prevents sibling component effects from interfering with each other,
1732 // e.g. a destroy function in one component should never override a ref set
1733 // by a create function in another component during the same commit.
1734 if (shouldProfile(finishedWork)) {
2622 - try {
2623 - startLayoutEffectTimer();
2624 - commitHookEffectListUnmount(
2625 - HookLayout | HookHasEffect,
2626 - finishedWork,
2627 - finishedWork.return,
2628 - );
2629 - } catch (error) {
2630 - captureCommitPhaseError(finishedWork, finishedWork.return, error);
2631 - }
1735 + startLayoutEffectTimer();
1736 + commitHookEffectListUnmount(
1737 + HookLayout | HookHasEffect,
1738 + finishedWork,
1739 + finishedWork.return,
1740 + );
1741 recordLayoutEffectDuration(finishedWork);
1742 } else {
2634 - try {
2635 - commitHookEffectListUnmount(
2636 - HookLayout | HookHasEffect,
2637 - finishedWork,
2638 - finishedWork.return,
2639 - );
2640 - } catch (error) {
2641 - captureCommitPhaseError(finishedWork, finishedWork.return, error);
2642 - }
1743 + commitHookEffectListUnmount(
1744 + HookLayout | HookHasEffect,
1745 + finishedWork,
1746 + finishedWork.return,
1747 + );
1748 }
1749 }
1750 return;
@@ -2730,17 +1835,11 @@ function commitMutationEffectsOnFiber(
1835 );
1836 }
1837 } else if (newResource === null && finishedWork.stateNode !== null) {
2733 - try {
2734 - commitUpdate(
2735 - finishedWork.stateNode,
2736 - finishedWork.type,
2737 - current.memoizedProps,
2738 - finishedWork.memoizedProps,
2739 - finishedWork,
2740 - );
2741 - } catch (error) {
2742 - captureCommitPhaseError(finishedWork, finishedWork.return, error);
2743 - }
1838 + commitHostUpdate(
1839 + finishedWork,
1840 + finishedWork.memoizedProps,
1841 + current.memoizedProps,
1842 + );
1843 }
1844 }
1845 return;
@@ -2752,16 +1851,7 @@ function commitMutationEffectsOnFiber(
1851 if (flags & Update) {
1852 const previousWork = finishedWork.alternate;
1853 if (previousWork === null) {
2755 - const singleton = finishedWork.stateNode;
2756 - const props = finishedWork.memoizedProps;
2757 - // This was a new mount, we need to clear and set initial properties
2758 - clearSingleton(singleton);
2759 - acquireSingletonInstance(
2760 - finishedWork.type,
2761 - props,
2762 - singleton,
2763 - finishedWork,
2764 - );
1854 + commitHostSingleton(finishedWork);
1855 }
1856 }
1857 }
@@ -2784,30 +1874,20 @@ function commitMutationEffectsOnFiber(
1874 // rely on mutating the flag during commit. Like by setting a flag
1875 // during the render phase instead.
1876 if (finishedWork.flags & ContentReset) {
2787 - const instance: Instance = finishedWork.stateNode;
2788 - try {
2789 - resetTextContent(instance);
2790 - } catch (error) {
2791 - captureCommitPhaseError(finishedWork, finishedWork.return, error);
2792 - }
1877 + commitHostResetTextContent(finishedWork);
1878 }
1879
1880 if (flags & Update) {
1881 const instance: Instance = finishedWork.stateNode;
1882 if (instance != null) {
1883 // Commit the work prepared earlier.
2799 - const newProps = finishedWork.memoizedProps;
1884 // For hydration we reuse the update path but we treat the oldProps
1885 // as the newProps. The updatePayload will contain the real change in
1886 // this case.
1887 + const newProps = finishedWork.memoizedProps;
1888 const oldProps =
1889 current !== null ? current.memoizedProps : newProps;
2805 - const type = finishedWork.type;
2806 - try {
2807 - commitUpdate(instance, type, oldProps, newProps, finishedWork);
2808 - } catch (error) {
2809 - captureCommitPhaseError(finishedWork, finishedWork.return, error);
2810 - }
1890 + commitHostUpdate(finishedWork, newProps, oldProps);
1891 }
1892 }
1893
@@ -2840,7 +1920,6 @@ function commitMutationEffectsOnFiber(
1920 );
1921 }
1922
2843 - const textInstance: TextInstance = finishedWork.stateNode;
1923 const newText: string = finishedWork.memoizedProps;
1924 // For hydration we reuse the update path but we treat the oldProps
1925 // as the newProps. The updatePayload will contain the real change in
@@ -2848,11 +1927,7 @@ function commitMutationEffectsOnFiber(
1927 const oldText: string =
1928 current !== null ? current.memoizedProps : newText;
1929
2851 - try {
2852 - commitTextUpdate(textInstance, oldText, newText);
2853 - } catch (error) {
2854 - captureCommitPhaseError(finishedWork, finishedWork.return, error);
2855 - }
1930 + commitHostTextUpdate(finishedWork, newText, oldText);
1931 }
1932 }
1933 return;
@@ -2878,26 +1953,12 @@ function commitMutationEffectsOnFiber(
1953 if (current !== null) {
1954 const prevRootState: RootState = current.memoizedState;
1955 if (prevRootState.isDehydrated) {
2881 - try {
2882 - commitHydratedContainer(root.containerInfo);
2883 - } catch (error) {
2884 - captureCommitPhaseError(
2885 - finishedWork,
2886 - finishedWork.return,
2887 - error,
2888 - );
2889 - }
1956 + commitHostHydratedContainer(root, finishedWork);
1957 }
1958 }
1959 }
1960 if (supportsPersistence) {
2894 - const containerInfo = root.containerInfo;
2895 - const pendingChildren = root.pendingChildren;
2896 - try {
2897 - replaceContainerChildren(containerInfo, pendingChildren);
2898 - } catch (error) {
2899 - captureCommitPhaseError(finishedWork, finishedWork.return, error);
2900 - }
1961 + commitHostRootContainerChildren(root, finishedWork);
1962 }
1963 }
1964
@@ -2933,14 +1994,11 @@ function commitMutationEffectsOnFiber(
1994
1995 if (flags & Update) {
1996 if (supportsPersistence) {
2936 - const portal = finishedWork.stateNode;
2937 - const containerInfo = portal.containerInfo;
2938 - const pendingChildren = portal.pendingChildren;
2939 - try {
2940 - replaceContainerChildren(containerInfo, pendingChildren);
2941 - } catch (error) {
2942 - captureCommitPhaseError(finishedWork, finishedWork.return, error);
2943 - }
1997 + commitHostPortalContainerChildren(
1998 + finishedWork.stateNode,
1999 + finishedWork,
2000 + finishedWork.stateNode.pendingChildren,
2001 + );
2002 }
2003 }
2004 return;
@@ -3138,11 +2196,7 @@ function commitReconciliationEffects(finishedWork: Fiber) {
2196 // before the effects on this fiber have fired.
2197 const flags = finishedWork.flags;
2198 if (flags & Placement) {
3141 - try {
3142 - commitPlacement(finishedWork);
3143 - } catch (error) {
3144 - captureCommitPhaseError(finishedWork, finishedWork.return, error);
3145 - }
2199 + commitHostPlacement(finishedWork);
2200 // Clear the "placement" from effect tag so that we know that this is
2201 // inserted, before any life-cycles like componentDidMount gets called.
2202 // TODO: findDOMNode doesn't rely on this any more but isMounted does
@@ -3338,23 +2392,9 @@ export function reappearLayoutEffects(
2392 includeWorkInProgressEffects,
2393 );
2394
3341 - // TODO: Check for LayoutStatic flag
3342 - const instance = finishedWork.stateNode;
3343 - if (typeof instance.componentDidMount === 'function') {
3344 - try {
3345 - instance.componentDidMount();
3346 - } catch (error) {
3347 - captureCommitPhaseError(finishedWork, finishedWork.return, error);
3348 - }
3349 - }
2395 + commitClassDidMount(finishedWork);
2396
3351 - // Commit any callbacks that would have fired while the component
3352 - // was hidden.
3353 - const updateQueue: UpdateQueue<mixed> | null =
3354 - (finishedWork.updateQueue: any);
3355 - if (updateQueue !== null) {
3356 - commitHiddenCallbacks(updateQueue, instance);
3357 - }
2397 + commitClassHiddenCallbacks(finishedWork);
2398
2399 // If this is newly finished work, check for setState callbacks
2400 if (includeWorkInProgressEffects && flags & Callback) {
@@ -3385,7 +2425,7 @@ export function reappearLayoutEffects(
2425 // These effects should only be committed when components are first mounted,
2426 // aka when there is no current/alternate.
2427 if (includeWorkInProgressEffects && current === null && flags & Update) {
3388 - commitHostComponentMount(finishedWork);
2428 + commitHostMount(finishedWork);
2429 }
2430
2431 // TODO: Check flags & Ref
@@ -3400,7 +2440,36 @@ export function reappearLayoutEffects(
2440 );
2441 // TODO: Figure out how Profiler updates should work with Offscreen
2442 if (includeWorkInProgressEffects && flags & Update) {
3403 - commitProfilerUpdate(finishedWork, current);
2443 + const {effectDuration} = finishedWork.stateNode;
2444 +
2445 + commitProfilerUpdate(
2446 + finishedWork,
2447 + current,
2448 + getCommitTime(),
2449 + effectDuration,
2450 + );
2451 +
2452 + // Schedule a passive effect for this Profiler to call onPostCommit hooks.
2453 + // This effect should be scheduled even if there is no onPostCommit callback for this Profiler,
2454 + // because the effect is also where times bubble to parent Profilers.
2455 + enqueuePendingPassiveProfilerEffect(finishedWork);
2456 +
2457 + // Propagate layout effect durations to the next nearest Profiler ancestor.
2458 + // Do not reset these values until the next render so DevTools has a chance to read them first.
2459 + let parentFiber = finishedWork.return;
2460 + outer: while (parentFiber !== null) {
2461 + switch (parentFiber.tag) {
2462 + case HostRoot:
2463 + const root = parentFiber.stateNode;
2464 + root.effectDuration += effectDuration;
2465 + break outer;
2466 + case Profiler:
2467 + const parentStateNode = parentFiber.stateNode;
2468 + parentStateNode.effectDuration += effectDuration;
2469 + break outer;
2470 + }
2471 + parentFiber = parentFiber.return;
2472 + }
2473 }
2474 break;
2475 }
@@ -3411,9 +2480,8 @@ export function reappearLayoutEffects(
2480 includeWorkInProgressEffects,
2481 );
2482
3414 - // TODO: Figure out how Suspense hydration callbacks should work
3415 - // with Offscreen.
2483 if (includeWorkInProgressEffects && flags & Update) {
2484 + // TODO: Delete this feature.
2485 commitSuspenseHydrationCallbacks(finishedRoot, finishedWork);
2486 }
2487 break;
@@ -3482,27 +2550,6 @@ function recursivelyTraverseReappearLayoutEffects(
2550 }
2551 }
2552
3485 -function commitHookPassiveMountEffects(
3486 - finishedWork: Fiber,
3487 - hookFlags: HookFlags,
3488 -) {
3489 - if (shouldProfile(finishedWork)) {
3490 - startPassiveEffectTimer();
3491 - try {
3492 - commitHookEffectListMount(hookFlags, finishedWork);
3493 - } catch (error) {
3494 - captureCommitPhaseError(finishedWork, finishedWork.return, error);
3495 - }
3496 - recordPassiveEffectDuration(finishedWork);
3497 - } else {
3498 - try {
3499 - commitHookEffectListMount(hookFlags, finishedWork);
3500 - } catch (error) {
3501 - captureCommitPhaseError(finishedWork, finishedWork.return, error);
3502 - }
3503 - }
3504 -}
3505 -
2553 function commitOffscreenPassiveMountEffects(
2554 current: Fiber | null,
2555 finishedWork: Fiber,
@@ -4341,28 +3388,6 @@ function detachAlternateSiblings(parentFiber: Fiber) {
3388 }
3389 }
3390
4344 -function commitHookPassiveUnmountEffects(
4345 - finishedWork: Fiber,
4346 - nearestMountedAncestor: null | Fiber,
4347 - hookFlags: HookFlags,
4348 -) {
4349 - if (shouldProfile(finishedWork)) {
4350 - startPassiveEffectTimer();
4351 - commitHookEffectListUnmount(
4352 - hookFlags,
4353 - finishedWork,
4354 - nearestMountedAncestor,
4355 - );
4356 - recordPassiveEffectDuration(finishedWork);
4357 - } else {
4358 - commitHookEffectListUnmount(
4359 - hookFlags,
4360 - finishedWork,
4361 - nearestMountedAncestor,
4362 - );
4363 - }
4364 -}
4365 -
3391 function recursivelyTraversePassiveUnmountEffects(parentFiber: Fiber): void {
3392 // Deletions effects can be scheduled on any fiber type. They need to happen
3393 // before the children effects have fired.
@@ -4696,7 +3721,7 @@ function commitPassiveUnmountInsideDeletedTreeOnFiber(
3721 }
3722 }
3723
4699 -function invokeLayoutEffectMountInDEV(fiber: Fiber): void {
3724 +export function invokeLayoutEffectMountInDEV(fiber: Fiber): void {
3725 if (__DEV__) {
3726 // We don't need to re-check StrictEffectsMode here.
3727 // This function is only called if that check has already passed.
@@ -4704,29 +3729,18 @@ function invokeLayoutEffectMountInDEV(fiber: Fiber): void {
3729 case FunctionComponent:
3730 case ForwardRef:
3731 case SimpleMemoComponent: {
4707 - try {
4708 - commitHookEffectListMount(HookLayout | HookHasEffect, fiber);
4709 - } catch (error) {
4710 - captureCommitPhaseError(fiber, fiber.return, error);
4711 - }
3732 + commitHookEffectListMount(HookLayout | HookHasEffect, fiber);
3733 break;
3734 }
3735 case ClassComponent: {
4715 - const instance = fiber.stateNode;
4716 - if (typeof instance.componentDidMount === 'function') {
4717 - try {
4718 - instance.componentDidMount();
4719 - } catch (error) {
4720 - captureCommitPhaseError(fiber, fiber.return, error);
4721 - }
4722 - }
3736 + commitClassDidMount(fiber);
3737 break;
3738 }
3739 }
3740 }
3741 }
3742
4729 -function invokePassiveEffectMountInDEV(fiber: Fiber): void {
3743 +export function invokePassiveEffectMountInDEV(fiber: Fiber): void {
3744 if (__DEV__) {
3745 // We don't need to re-check StrictEffectsMode here.
3746 // This function is only called if that check has already passed.
@@ -4734,18 +3748,14 @@ function invokePassiveEffectMountInDEV(fiber: Fiber): void {
3748 case FunctionComponent:
3749 case ForwardRef:
3750 case SimpleMemoComponent: {
4737 - try {
4738 - commitHookEffectListMount(HookPassive | HookHasEffect, fiber);
4739 - } catch (error) {
4740 - captureCommitPhaseError(fiber, fiber.return, error);
4741 - }
3751 + commitHookEffectListMount(HookPassive | HookHasEffect, fiber);
3752 break;
3753 }
3754 }
3755 }
3756 }
3757
4748 -function invokeLayoutEffectUnmountInDEV(fiber: Fiber): void {
3758 +export function invokeLayoutEffectUnmountInDEV(fiber: Fiber): void {
3759 if (__DEV__) {
3760 // We don't need to re-check StrictEffectsMode here.
3761 // This function is only called if that check has already passed.
@@ -4753,15 +3763,11 @@ function invokeLayoutEffectUnmountInDEV(fiber: Fiber): void {
3763 case FunctionComponent:
3764 case ForwardRef:
3765 case SimpleMemoComponent: {
4756 - try {
4757 - commitHookEffectListUnmount(
4758 - HookLayout | HookHasEffect,
4759 - fiber,
4760 - fiber.return,
4761 - );
4762 - } catch (error) {
4763 - captureCommitPhaseError(fiber, fiber.return, error);
4764 - }
3766 + commitHookEffectListUnmount(
3767 + HookLayout | HookHasEffect,
3768 + fiber,
3769 + fiber.return,
3770 + );
3771 break;
3772 }
3773 case ClassComponent: {
@@ -4775,7 +3781,7 @@ function invokeLayoutEffectUnmountInDEV(fiber: Fiber): void {
3781 }
3782 }
3783
4778 -function invokePassiveEffectUnmountInDEV(fiber: Fiber): void {
3784 +export function invokePassiveEffectUnmountInDEV(fiber: Fiber): void {
3785 if (__DEV__) {
3786 // We don't need to re-check StrictEffectsMode here.
3787 // This function is only called if that check has already passed.
@@ -4783,25 +3789,12 @@ function invokePassiveEffectUnmountInDEV(fiber: Fiber): void {
3789 case FunctionComponent:
3790 case ForwardRef:
3791 case SimpleMemoComponent: {
4786 - try {
4787 - commitHookEffectListUnmount(
4788 - HookPassive | HookHasEffect,
4789 - fiber,
4790 - fiber.return,
4791 - );
4792 - } catch (error) {
4793 - captureCommitPhaseError(fiber, fiber.return, error);
4794 - }
3792 + commitHookEffectListUnmount(
3793 + HookPassive | HookHasEffect,
3794 + fiber,
3795 + fiber.return,
3796 + );
3797 }
3798 }
3799 }
3800 }
4799 -
4800 -export {
4801 - commitPlacement,
4802 - commitAttachRef,
4803 - invokeLayoutEffectMountInDEV,
4804 - invokeLayoutEffectUnmountInDEV,
4805 - invokePassiveEffectMountInDEV,
4806 - invokePassiveEffectUnmountInDEV,
4807 -};