Skip to main content

How Rendering components works

In the previous section (how begin work works), we left a big unexplained switch statement. The goal now is to explore it.

Let's put back the switch before starting:

// simplified
function beginWork(
current: Fiber | null,
workInProgress: Fiber,
renderLanes: Lanes,
) {

// previous code

switch(workInProgress.tag) {
// case FunctionComponent:
// case ClassComponent:
// case IndeterminateComponent:
// case HostRoot:
// case HostPortal:
// case HostComponent:
// case HostText:
// case Fragment:
// case Mode:
// case ContextConsumer:
// case ContextProvider:
// case ForwardRef:
// case Profiler:
// case SuspenseComponent:
// case MemoComponent:
// case SimpleMemoComponent:
// case LazyComponent:
// case IncompleteClassComponent:
// case DehydratedFragment:
// case SuspenseListComponent:
// case ScopeComponent:
// case OffscreenComponent:
// case LegacyHiddenComponent:
// case CacheComponent:
// case TracingMarkerComponent:
// case HostHoistable:
// case HostSingleton:
}

throw new Error('...');
}
note

This section would assume a very important thing, we will explain how each work tag is rendered, but it is very important to know that, during the reconcile children step of render, the next child's alternate is created, which will be the workInProgress.

On the first mount, the workInProgerss being the alternate and current being null at this point. But later on updates, current is defined.

So if you are wondering but when the first fiber was created ? the previous section ended with a note mentioning the creation of the third fiber when coming from root.render() which will be the alternate of the very first child given that render received.

How rendering by WorkTag works

beginWork will redirect to the function that's specialized in rendering by the fiber's tag.

How rendering Function Component works

We will start by the most common way to create components: function components.

switch(workInProgress.tag) {
case FunctionComponent: {
// 1 Component is your function component
// workInProgress is the alternate of this tree
// current === null means: this component is mounting for the first time
const Component = workInProgress.type;
// 2 pendingProps are the next props that the component will render with
const unresolvedProps = workInProgress.pendingProps;
// 3 resolveDefaultProps will happen when Component changed type
const resolvedProps =
workInProgress.elementType === Component
? unresolvedProps
: resolveDefaultProps(Component, unresolvedProps);
// 4 render the component
return updateFunctionComponent(
current, // Fiber | null: the current rendered fiber
workInProgress, // its alternate (at first, it is created before current)
Component, // the function component
resolvedProps, // component props
renderLanes, // the render lanes
);
}
}

So this is how your function components gets rendered. Let's break the top level code before entering into updateFunctionComponent

  1. The first step consists of referencing the workInProgress fiber type.
  2. The pendingProps are called unresolvedProps because we may need to add the defaultProps to them next.
  3. In case the workInProgress.elementType is different from workInProgress.type then we will resolve the default props from the workInProgress.type.
  4. return the real rendering work via updateFunctionComponent
note

The first mount of function components doesn't pass through the FunctionComponent case, but rather through the IndeterminateComponent.

Because initially when React creates a Fiber from Element it marks it as IndeterminateComponent and when attempting to render it, it will mark it as FunctionComponent as we will see in the case related to this.

So updateFunctionComponent is the function that will re-render our function components, let's see what it looks like:

// simplified
function updateFunctionComponent(
current: null | Fiber, // current rendered fiber
workInProgress: Fiber, // alternate (wip)
Component: any, // the function Component
nextProps: any, // the new props
renderLanes: Lanes, // the render lanes (DefaultLane, SyncLane, Transition...)
): Fiber | null {
// [...] some legacy context management

// 1
// prepare to read context inside this component
prepareToReadContext(workInProgress, renderLanes);

// 2
// You were probably waiting to see this function from the beginning 😅
// Note: rendering function components from Indeterminate will call this
// function too.
let nextChildren = renderWithHooks(
current,
workInProgress,
Component,
nextProps,
// you can ignore this.
// you insist ? it is the second arg to your function component 🙂
context,
renderLanes,
);

if (current !== null && !didReceiveUpdate) {
bailoutHooks(current, workInProgress, renderLanes);
return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
}

workInProgress.flags |= PerformedWork;
// 3
// We've seen this before 😉 and we will see it again and again and again
reconcileChildren(current, workInProgress, nextChildren, renderLanes);
return workInProgress.child;
}

The major steps for rendering a function component as seen above are:

Prepare to read context

When rendering a function component, you may use the useContext hook to subscribe to the nearest ReactContext.

The calls to useContext will actually register the desired context in the fiber.dependencies linked list.

Before rendering, React will empty this list in the alternate and during render it will stack them again (React won't care if you change which context you subscribe to.).

render with hooks

Let's put you into context with its signature and how we called it first:

export function renderWithHooks<Props, SecondArg>(
current: Fiber | null,
workInProgress: Fiber,
Component: (p: Props, arg: SecondArg) => any,
props: Props,
secondArg: SecondArg,
nextRenderLanes: Lanes,
): any {
// [Not Native Code]
}


// we called it like this
let nextChildren = renderWithHooks(
current,
workInProgress,
Component,
nextProps,
context,
renderLanes,
);

Let's scratch its implementation:


// simplified
export function renderWithHooks<Props, SecondArg>(
current: Fiber | null,
workInProgress: Fiber,
Component: (p: Props, arg: SecondArg) => any,
props: Props,
secondArg: SecondArg,
nextRenderLanes: Lanes,
): any {
// two module internal states
renderLanes = nextRenderLanes;
currentlyRenderingFiber = workInProgress;


// reset some workInProgress properties (this is an alternate)
workInProgress.memoizedState = null;
workInProgress.updateQueue = null;
workInProgress.lanes = NoLanes;

// 1
// This step is so important in react hooks
ReactCurrentDispatcher.current =
// this means that the component is mounting for the first time
current === null || current.memoizedState === null
? HooksDispatcherOnMount
: HooksDispatcherOnUpdate;

const shouldDoubleRenderInDev = __DEV__ && (workInProgress.mode & StrictMode);

// 2
let children = Component(props, secondArg);

// 3
if (didScheduleRenderPhaseUpdateDuringThisPass) {
children = renderWithHooksAgain(
workInProgress,
Component,
props,
secondArg,
);
}

// 4
if (shouldDoubleRenderInDev) {
children = renderWithHooksAgain(
workInProgress,
Component,
props,
secondArg,
);
}

// 5
finishRenderingHooks(current, workInProgress, Component);

// 6
return children;
}

Let's explain and dive into the major steps above:

  1. The first thing to do when rendering a function component is to set the right dispatcher:

    1. When the component is mounting for the first time, then HooksDispatcherOnMount is used.
    2. When the component is updating, then HooksDispatcherOnUpdate is used. These dispatchers are explained in details in the hooks section.

    The information you would need about the dispatcher in this section is that all exported hooks will start by resolving what's the current dispatcher (the one React set before rendering your component) then return the same function from the dispatcher.

    // almost all the hooks do this pattern
    function useSomeHook() {
    const dispatcher = ReactCurrentDispatcher.current;
    return dispatcher.useSomeHook();
    }
  2. The second step is rendering the actual component and taking its return:

    let children = Component(props, secondArg);

    The second arg is the context (or pendingContext) property from the fiberRoot of this tree.

    note

    By here, all your hooks got executed.

  3. React will keep track of whether the current rendering component did schedule a render phase update, and if it is the case, it will render again:

    renderWithHooksAgain is a function that ticks if a function component did schedule a render phase update or when replaying the render of a component in dev due to StrictMode.

    The goal of render again is to keep track of nested updates and infinite loops, and also it will set a different dispatcher that would change the behavior of some hooks (more on this in hooks section).

    function renderWithHooksAgain<Props, SecondArg>(
    workInProgress: Fiber,
    Component: (p: Props, arg: SecondArg) => any,
    props: Props,
    secondArg: SecondArg,
    ): any {
    let numberOfRerenders = 0;
    let children;
    do {
    // some code related to thenables and use() hook
    didScheduleRenderPhaseUpdateDuringThisPass = false;

    if (numberOfReRenders >= RE_RENDER_LIMIT) {
    throw new Error(
    'Too many re-renders. React limits the number of renders to prevent ' +
    'an infinite loop.',
    );
    }

    numberOfReRenders += 1;
    currentHook = null;
    workInProgressHook = null;
    workInProgress.updateQueue = null;


    ReactCurrentDispatcher.current = __DEV__
    ? HooksDispatcherOnRerenderInDEV
    : HooksDispatcherOnRerender;

    children = Component(props, secondArg);
    } while (didScheduleRenderPhaseUpdateDuringThisPass);

    return children;
    }

    renderWithHooksAgain will keep rendering your component until there it won't schedule a render phase update.

  4. Similarly, when StrictMode is enabled, it will call renderWithHooksAgain again to perform another render.

  5. Call finishRenderingHooks function:

    finishRenderingHooks(current, workInProgress, Component);

    This function will:

    1. Reset the ReactCurrentDispatcher to ContextOnlyDispatcher
    2. Reset hooks variables (currentHook, wipHook)
    3. Reset some thenable and use states
    4. Throw when fewer hooks were used
  6. The last step in renderWithHooks is to return the resulted children.

    return children;

Reconcile children

After rendering the component and obtaining the next children, then it is time to reconcile with the previous tree's children.

let nextChildren = renderWithHooks(...);

reconcileChildren(current, workInProgress, nextChildren, renderLanes);

return workInProgress.child;

As seen previously, reconcile children will redirect the work to a reconciliation function depending on whether the component is mounting or updating.

The goal of reconcileChildren is to create an alternate for the first child which will be processed next.

In the case where this child fiber is null (we've reached the bottom of the current tree), then if you remember from performUnitOfWork, it will call completeWork when the next fiber to work on is null.

So, inside React, you just saw step by step how a function component gets rendered.

How rendering Class Components works

How rendering Indeterminate Component works

This section is not available yet. Please fill an issue.

While waiting for the newsletter, you can get notified when new content drops by following me on X.

How rendering Host Root works

This section is not available yet. Please fill an issue.

While waiting for the newsletter, you can get notified when new content drops by following me on X.

How rendering Host Portal works

This section is not available yet. Please fill an issue.

While waiting for the newsletter, you can get notified when new content drops by following me on X.

How rendering Host Component works

This section is not available yet. Please fill an issue.

While waiting for the newsletter, you can get notified when new content drops by following me on X.

How rendering Host Text works

This section is not available yet. Please fill an issue.

While waiting for the newsletter, you can get notified when new content drops by following me on X.

How rendering Fragment works

This section is not available yet. Please fill an issue.

While waiting for the newsletter, you can get notified when new content drops by following me on X.

How rendering Mode works

This section is not available yet. Please fill an issue.

While waiting for the newsletter, you can get notified when new content drops by following me on X.

How rendering Context Consumer works

This section is not available yet. Please fill an issue.

While waiting for the newsletter, you can get notified when new content drops by following me on X.

How rendering Context Provider works

This section is not available yet. Please fill an issue.

While waiting for the newsletter, you can get notified when new content drops by following me on X.

How rendering Forward Ref works

This section is not available yet. Please fill an issue.

While waiting for the newsletter, you can get notified when new content drops by following me on X.

How rendering Profiler works

This section is not available yet. Please fill an issue.

While waiting for the newsletter, you can get notified when new content drops by following me on X.

How rendering Suspense works

This section is not available yet. Please fill an issue.

While waiting for the newsletter, you can get notified when new content drops by following me on X.

How rendering Memo Component works

This section is not available yet. Please fill an issue.

While waiting for the newsletter, you can get notified when new content drops by following me on X.

How rendering Lazy Component works

This section is not available yet. Please fill an issue.

While waiting for the newsletter, you can get notified when new content drops by following me on X.

How rendering Incomplete Class Component works

This section is not available yet. Please fill an issue.

While waiting for the newsletter, you can get notified when new content drops by following me on X.

How rendering Dehydrated Fragment works

This section is not available yet. Please fill an issue.

While waiting for the newsletter, you can get notified when new content drops by following me on X.

How rendering Suspense List works

This section is not available yet. Please fill an issue.

While waiting for the newsletter, you can get notified when new content drops by following me on X.

How rendering Scope Component works

This section is not available yet. Please fill an issue.

While waiting for the newsletter, you can get notified when new content drops by following me on X.

How rendering Offscreen Component works

This section is not available yet. Please fill an issue.

While waiting for the newsletter, you can get notified when new content drops by following me on X.

How rendering Legacy Hidden Component works

This section is not available yet. Please fill an issue.

While waiting for the newsletter, you can get notified when new content drops by following me on X.

How rendering Cache Component works

This section is not available yet. Please fill an issue.

While waiting for the newsletter, you can get notified when new content drops by following me on X.

How rendering Tracing Marker Component works

This section is not available yet. Please fill an issue.

While waiting for the newsletter, you can get notified when new content drops by following me on X.

How rendering Host Hoistable works

This section is not available yet. Please fill an issue.

While waiting for the newsletter, you can get notified when new content drops by following me on X.

How rendering Host Singleton works

This section is not available yet. Please fill an issue.

While waiting for the newsletter, you can get notified when new content drops by following me on X.