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('...');
}
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
- The first step consists of referencing the
workInProgress
fibertype
. - The
pendingProps
are calledunresolvedProps
because we may need to add the defaultProps to them next. - In case the
workInProgress.elementType
is different fromworkInProgress.type
then we will resolve the default props from theworkInProgress.type
. - return the real rendering work via
updateFunctionComponent
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:
The first thing to do when rendering a function component is to set the right dispatcher:
- When the component is mounting for the first time, then
HooksDispatcherOnMount
is used. - 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();
}- When the component is mounting for the first time, then
The second step is rendering the actual component and taking its return:
let children = Component(props, secondArg);
The second arg is the
context
(orpendingContext
) property from thefiberRoot
of this tree.noteBy here, all your hooks got executed.
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 toStrictMode
.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.
Similarly, when
StrictMode
is enabled, it will callrenderWithHooksAgain
again to perform another render.Call
finishRenderingHooks
function:finishRenderingHooks(current, workInProgress, Component);
This function will:
- Reset the
ReactCurrentDispatcher
toContextOnlyDispatcher
- Reset hooks variables (currentHook, wipHook)
- Reset some thenable and use states
- Throw when fewer hooks were used
- Reset the
The last step in
renderWithHooks
is to return the resultedchildren
.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.