Frontend Development 23 min read

Understanding React Fiber Architecture: Reconciliation, Scheduling, and Commit Phases

React Fiber replaces the old Stack Reconciler with a linked‑list of Fiber nodes that enable incremental, pause‑and‑resume rendering, priority scheduling, and a three‑step commit phase, allowing smoother asynchronous updates, better performance, and the foundation for Concurrent Mode and Suspense.

NetEase Cloud Music Tech Team
NetEase Cloud Music Tech Team
NetEase Cloud Music Tech Team
Understanding React Fiber Architecture: Reconciliation, Scheduling, and Commit Phases

Preface

React v16.13 introduced the experimental Concurrent Mode and the new Suspense mechanism, which naturally solves long‑standing asynchronous side‑effect problems. Combined with Hooks (v16.8) and the Fiber architecture (v16.0), React provides a much smoother developer and user experience. This article explores the inner workings of React Fiber based on the v16.8.6 source code.

Stack Reconciler vs. Fiber Reconciler

Stack Reconciler was the coordination algorithm used in React v15 and earlier. Starting with v16, React Fiber rewrote the Stack Reconciler with a virtual stack‑frame model, allowing the traversal to be paused, aborted, or resumed. The original Stack Reconciler performed synchronous DOM updates, which could cause janky animations and poor UX. Fiber replaces the built‑in call stack with a linked‑list of Fiber nodes, each representing a unit of work.

Basic Concepts

Work

Any computation that must be performed during reconciliation—state updates, prop updates, ref updates, etc.—is collectively called work .

Fiber Object

Each React element corresponds to a Fiber object. A fiber stores component‑specific data and links to other fibers via child , sibling , and return pointers, forming a tree‑like linked list.

Fiber = {
    // tag identifies the type of fiber (see WorkTag)
    tag: WorkTag,

    // parent fiber
    return: Fiber | null,

    // first child fiber
    child: Fiber | null,

    // next sibling fiber
    sibling: Fiber | null,

    // props set at the beginning of work
    pendingProps: any,

    // props after work finishes
    memoizedProps: any,

    // current state
    memoizedState: any,

    // effect type (see EffectTag)
    effectTag: SideEffectTag,

    // pointer to the next effect in the list
    nextEffect: Fiber | null,

    // first effect in the list
    firstEffect: Fiber | null,

    // last effect in the list
    lastEffect: Fiber | null,

    // expiration time used for priority ordering
    expirationTime: ExpirationTime,
};

WorkTag

export const FunctionComponent = 0;
export const ClassComponent = 1;
export const IndeterminateComponent = 2; // before we know if it is function or class
export const HostRoot = 3; // root of a host tree
export const HostPortal = 4;
export const HostComponent = 5;
export const HostText = 6;
export const Fragment = 7;
export const Mode = 8;
export const ContextConsumer = 9;
export const ContextProvider = 10;
export const ForwardRef = 11;
export const Profiler = 12;
export const SuspenseComponent = 13;
export const MemoComponent = 14;
export const SimpleMemoComponent = 15;
export const LazyComponent = 16;
export const IncompleteClassComponent = 17;
export const DehydratedSuspenseComponent = 18;
export const EventComponent = 19;
export const EventTarget = 20;
export const SuspenseListComponent = 21;

EffectTag

export const NoEffect = 0b000000000000;
export const PerformedWork = 0b000000000001;
export const Placement = 0b000000000010;
export const Update = 0b000000000100;
export const PlacementAndUpdate = 0b000000000110;
export const Deletion = 0b000000001000;
export const ContentReset = 0b000000010000;
export const Callback = 0b000000100000;
export const DidCapture = 0b000001000000;
export const Ref = 0b000010000000;
export const Snapshot = 0b000100000000;
export const Passive = 0b001000000000;
export const LifecycleEffectMask = 0b001110100100;
export const HostEffectMask = 0b001111111111;
export const Incomplete = 0b010000000000;
export const ShouldCapture = 0b100000000000;

Reconciliation and Scheduling

Reconciliation uses a diff algorithm to compare the virtual DOM and determine which parts need to change.

Scheduling decides when a piece of work should be executed, based on priority derived from expirationTime .

Render Phase

The render phase builds a new workInProgress tree. It can be paused and resumed because each Fiber stores its own context.

enqueueSetState

function Component(props, context, updater) {
    this.props = props;
    this.context = context;
    this.updater = updater || ReactNoopUpdateQueue;
}

Component.prototype.setState = function (partialState, callback) {
    this.updater.enqueueSetState(this, partialState, callback, 'setState');
};

Priority Constants

export const NoPriority = 0;
export const ImmediatePriority = 1;
export const UserBlockingPriority = 2;
export const NormalPriority = 3;
export const LowPriority = 4;
export const IdlePriority = 5;

renderRoot

function renderRoot(root, expirationTime, isSync) | null {
  do {
    // highest priority – sync branch
    if (isSync) {
      workLoopSync();
    } else {
      workLoop();
    }
  } while (true);
}

function workLoop() {
  while (workInProgress !== null && !shouldYield()) {
    workInProgress = performUnitOfWork(workInProgress);
  }
}

performUnitOfWork

function performUnitOfWork(unitOfWork) {
  const current = unitOfWork.alternate;
  let next;
  next = beginWork(current, unitOfWork, renderExpirationTime);
  if (next === null) {
    next = completeUnitOfWork(unitOfWork);
  }
  return next;
}

completeUnitOfWork

function completeUnitOfWork(unitOfWork) {
  // depth‑first search
  workInProgress = unitOfWork;
  do {
    const current = workInProgress.alternate;
    const returnFiber = workInProgress.return;
    // build effect‑list part
    if (returnFiber.firstEffect === null) {
      returnFiber.firstEffect = workInProgress.firstEffect;
    }
    if (workInProgress.lastEffect !== null) {
      if (returnFiber.lastEffect !== null) {
        returnFiber.lastEffect.nextEffect = workInProgress.firstEffect;
      }
      returnFiber.lastEffect = workInProgress.lastEffect;
    }
    if (returnFiber.lastEffect !== null) {
      returnFiber.lastEffect.nextEffect = workInProgress;
    } else {
      returnFiber.firstEffect = workInProgress;
    }
    returnFiber.lastEffect = workInProgress;
    const siblingFiber = workInProgress.sibling;
    if (siblingFiber !== null) {
      return siblingFiber;
    }
    workInProgress = returnFiber;
  } while (workInProgress !== null);
}

Commit Phase

The commit phase applies the effect list to the real DOM and runs lifecycle methods. It consists of three sub‑phases: before‑mutation, mutation, and layout.

commitRootImpl

function commitRootImpl(root) {
  if (firstEffect !== null) {
    // before‑mutation phase
    do {
      try {
        commitBeforeMutationEffects();
      } catch (error) {
        nextEffect = nextEffect.nextEffect;
      }
    } while (nextEffect !== null);

    // mutation phase
    nextEffect = firstEffect;
    do {
      try {
        commitMutationEffects();
      } catch (error) {
        nextEffect = nextEffect.nextEffect;
      }
    } while (nextEffect !== null);

    // replace work‑in‑progress with current tree
    root.current = finishedWork;

    // layout phase
    nextEffect = firstEffect;
    do {
      try {
        commitLayoutEffects(root, expirationTime);
      } catch (error) {
        captureCommitPhaseError(nextEffect, error);
        nextEffect = nextEffect.nextEffect;
      }
    } while (nextEffect !== null);

    nextEffect = null;
  } else {
    // No effects.
    root.current = finishedWork;
  }
}

commitBeforeMutationEffects (ClassComponent example)

function commitBeforeMutationLifeCycles(current, finishedWork) {
  switch (finishedWork.tag) {
    case ClassComponent: {
      if (finishedWork.effectTag & Snapshot) {
        if (current !== null) {
          const prevProps = current.memoizedProps;
          const prevState = current.memoizedState;
          const instance = finishedWork.stateNode;
          const snapshot = instance.getSnapshotBeforeUpdate(
            finishedWork.elementType === finishedWork.type
              ? prevProps
              : resolveDefaultProps(finishedWork.type, prevProps),
            prevState,
          );
          instance.__reactInternalSnapshotBeforeUpdate = snapshot;
        }
      }
      return;
    }
    // other tags omitted for brevity
  }
}

commitMutationEffects

function commitMutationEffects() {
  while (nextEffect !== null) {
    const effectTag = nextEffect.effectTag;
    let primaryEffectTag = effectTag & (Placement | Update | Deletion);
    switch (primaryEffectTag) {
      case Placement:
        // ...
        break;
      case PlacementAndUpdate:
        // ...
        break;
      case Update: {
        const current = nextEffect.alternate;
        commitWork(current, nextEffect);
        break;
      }
      case Deletion:
        commitDeletion(nextEffect);
        break;
    }
  }
}

commitLayoutEffects (ClassComponent example)

function commitLifeCycles(finishedRoot, current, finishedWork, committedExpirationTime) {
  switch (finishedWork.tag) {
    case ClassComponent: {
      const instance = finishedWork.stateNode;
      if (finishedWork.effectTag & Update) {
        if (current === null) {
          instance.componentDidMount();
        } else {
          const prevProps =
            finishedWork.elementType === finishedWork.type
              ? current.memoizedProps
              : resolveDefaultProps(finishedWork.type, current.memoizedProps);
          const prevState = current.memoizedState;
          instance.componentDidUpdate(
            prevProps,
            prevState,
            instance.__reactInternalSnapshotBeforeUpdate,
          );
        }
      }
      const updateQueue = finishedWork.updateQueue;
      if (updateQueue !== null) {
        commitUpdateQueue(
          finishedWork,
          updateQueue,
          instance,
          committedExpirationTime,
        );
      }
      return;
    }
    // other tags omitted for brevity
  }
}

Extensions

Additional topics include the call‑graph of Fiber functions, the evolution from requestIdleCallback to the custom Scheduler introduced in v16.10, and visualizations of the effect‑list construction.

Conclusion

Fiber is a core part of React’s design, enabling incremental rendering, pause‑and‑resume capabilities, and fine‑grained priority scheduling. Understanding Fiber helps developers grasp why certain lifecycle methods are deprecated and how future features like async rendering will be built.

References

react-fiber-architecture

In‑depth explanation of state and props update in React

In‑depth overview of the new reconciliation algorithm in React

The how and why on React’s usage of linked list in Fiber

Effect List – another Fiber linked‑list construction process

js‑ntqfill

reconciliationjavascriptReactSchedulingHooksFiberConcurrent Mode
NetEase Cloud Music Tech Team
Written by

NetEase Cloud Music Tech Team

Official account of NetEase Cloud Music Tech Team

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.