Suspense and Fiber: The Intricate Machinery Behind React's Rendering Elegance

Dimitrii Lyomin - Sep 16 - - Dev Community

React Fiber, the heart of React's concurrent rendering, enables the framework to break down tasks into smaller units and prioritize more important tasks, allowing for smoother and more responsive user interfaces. When paired with Suspense, it allows React to "suspend" rendering, displaying a fallback UI while waiting for tasks like data fetching or computations to finish.

A Fiber is a JavaScript object that represents a unit of work in React. It holds important information about a component during the rendering process:

All the code in that article is pseudo. You can find real files links in the end of the article

{
  tag,                // The type of fiber (e.g., HostComponent, FunctionComponent)
  stateNode,          // The actual DOM node or instance for class components
  memoizedProps,      // Props used during the last render
  memoizedState,      // State used during the last render
  return,             // Parent fiber
  sibling,            // Next sibling fiber
  child,              // First child fiber
  alternate,          // Link to the previous fiber (for reconciling updates)
}
Enter fullscreen mode Exit fullscreen mode

The fiber tree allows React to efficiently traverse the component tree, perform rendering work, and track updates, state, and DOM mutations.

Let's build a simple app consisting of Header, Content, and Footer components. To highlight how Fiber works, we’ll make Header perform a heavy computation.

function App() {
  return (
    <Suspense fallback={<div>Loading...</div>}>
      <Header />
      <Content />
      <Footer />
    </Suspense>
  );
}

function Header() {
  // Simulating heavy computation
  for (let i = 0; i < 1e9; i++) {}
  return <h1>Header with heavy computation</h1>;
}

function Content() {
  return <p>This is the content area</p>;
}

function Footer() {
  return <footer>Footer content</footer>;
}
Enter fullscreen mode Exit fullscreen mode

How it starts? The render function is responsible for starting the initial rendering process in React. It takes a root component (like App) and creates the initial fiber tree for React to work on:

function render(element, container) {
  const rootFiber = {
    tag: HostRoot,           // Root fiber tag
    stateNode: container,    // Reference to the DOM container
    child: null,             // Initial child will be added here
  };

  // Create a new work-in-progress fiber for our App component
  const appFiber = createFiberFromElement(element);
  rootFiber.child = appFiber;

  // Start the rendering process
  scheduleWork(rootFiber);
}
Enter fullscreen mode Exit fullscreen mode

Then work scheduling take into an action. scheduleWork is responsible for starting the work loop and processing the fiber tree. It determines when to start processing work, often using workLoop.

function scheduleWork(rootFiber) {
  nextUnitOfWork = rootFiber; // Set the root fiber as the starting point
  requestIdleCallback(workLoop); // Begin processing fibers when the browser is idle
}
Enter fullscreen mode Exit fullscreen mode

Here, the root fiber is set as the nextUnitOfWork, and React schedules the workLoop function to process it. React uses requestIdleCallback to wait until the browser is idle before starting the work, which is essential for non-blocking rendering.

As mentioned earlier, workLoop and performUnitOfWork handle the actual processing of fibers. This is Reconciliation Phase.

workLoop processes each fiber by calling performUnitOfWork until all tasks are complete or the browser needs to prioritize other tasks.

function workLoop() {
  while (nextUnitOfWork && !shouldYield()) {
    nextUnitOfWork = performUnitOfWork(nextUnitOfWork); // Perform work for the next fiber
  }

  if (!nextUnitOfWork) {
    // Commit all completed work to the DOM
    commitRoot();
  } else {
    // Yield control and schedule the next chunk of work
    requestIdleCallback(workLoop);
  }
}
Enter fullscreen mode Exit fullscreen mode

performUnitOfWork processes individual fibers, checking for Suspense boundaries, starting rendering tasks, and managing updates.

function performUnitOfWork(fiber) {
  const next = beginWork(fiber);

  // If there's more work to do, return the next unit
  if (next) {
    return next;
  }

  // Otherwise, complete the work and move to the next sibling or parent
  let sibling = fiber.sibling;
  if (sibling) {
    return sibling;
  }

  return fiber.return;
}

function beginWork(fiber) {
  if (fiber.tag === SuspenseComponent) {
    if (fiber.suspenseState === Suspended) {
      // Render fallback UI if data is not ready
      return fiber.fallback;
    } else if (fiber.suspenseState === Resolved) {
      // Render actual content once data resolves
      return fiber.child;
    }
  }

  // Continue rendering for other fibers
}
Enter fullscreen mode Exit fullscreen mode

After completing all the units of work, commitRoot is responsible for committing the fiber tree to the DOM, including handling Suspense fallback UI. This is Commit Phase.

function commitRoot() {
  let fiber = rootFiber;

  while (fiber) {
    if (fiber.tag === SuspenseComponent && fiber.suspenseState === Suspended) {
      // Commit fallback UI if Suspense is active
      updateDOM(fiber.stateNode);
    } else {
      // Commit normal content
      updateDOM(fiber.memoizedProps);
    }

    // If the fiber has a passive effect (i.e., useEffect), schedule it
    if (fiber.effectTag === Passive) {
    // Function to schedule the useEffect hooks to run after the DOM updates and browser paint
      scheduleEffectHooks(fiber);
    }

    fiber = fiber.next;  // Move to the next fiber in the tree
  }
}
Enter fullscreen mode Exit fullscreen mode

During the commit phase, React invokes scheduleEffectHooks for any fiber that has side effects (like useEffect). These hooks are deferred until after the DOM updates and browser paints to avoid blocking the rendering process. This allows React to perform non-blocking updates while ensuring that side-effects are executed in a timely manner once the UI is stable.

In this article, we explored the inner workings of React's Fiber architecture and how Suspense interacts with it to enhance the rendering process. We learned how React breaks down rendering tasks into smaller units, prioritizes important tasks, and defers less critical updates to keep the user interface smooth and responsive.

We gained a clear understanding of the fiber tree structure, the work scheduling process, and how reconciliation and commit phases work to update the DOM efficiently. We also explored how Suspense integrates within this architecture, displaying fallback content while awaiting data or heavy computations.

If you have any feedback or thoughts on the article—whether parts are unclear or could be improved—I’d be glad to hear them. Your insights will help refine and enhance the content for everyone.

Sources and original implementations:

  1. React Fiber Overview:
  2. React Suspense:
  3. Current Implementation of performUnitOfWork:
  4. Current Implementation of beginWork:
  5. Current Implementation of commitRoot:
  6. Current Implementation of scheduleWork and workLoop
. .
Terabox Video Player