Building Your Own React From Scratch (Part 2)

Integrating Fiber.

In the first part of this tutorial, we built a react clone but it did not have React Fiber. This second part of the tutorial will focus on integrating Fiber without breaking any changes for components built using the old version of the library.

What we can learn from Facebook’s React architecture

https://youtu.be/ZCuYPiUIONs

Check this video out to try and get a better grasp of React Fiber and how we will be implementing the same in our React clone.

Up until this point, our implementation has been made up of recursive calls that perform DOM operations. See reconcile and reconcilechildren . Whenever we make a change in the v-dom we make the corresponding change to the DOM.

With the new architecture, changes to the DOM are made at once when all the updates have been made in a second tree called the work-in-progress tree. The 1st tree is called the current tree and its the tree that represents the nodes in the actual DOM. Once all updates are complete on the work-in-progress tree it gets rendered to the DOM and becomes the current tree.

A code pen illustration of the computation from Lin Clark’s talk. Building our own demo could not fit in the scope of this tutorial so we’ll use a pre-made React demo that toggles Fiber on and off

We’ll re-write the reconciliation algorithm again and this time we’ll use requestIdleCallback. Through this function, the browser lets us know through a callback function how much time is left till it has to perform other tasks in its backlog. Once it’s time for the browser to do other things, we simply pause traversing the tree and resume once the browser has no work to do.

This new re-implementation will rely mostly on looping and not recursion just to make sure that we make the React clone run faster.

The structure of a Fiber

[pastacode lang="javascript" manual="%2F%2FNB%3A%20ALL%20DOM%20nodes%20have%20their%20corresponding%20fibers%20in%20our%20new%20implementation%0A%2F%2F%20most%20of%20this%20properties%20will%20make%20sense%20once%20we%20begin%20using%20them%0Alet%20fiber%20%3D%20%7B%0A%20%20tag%3A%20HOST_COMPONENT%2C%20%2F%2F%20we%20can%20have%20either%20host%20of%20class%20component%0A%20%20type%3A%20%22input%22%2C%0A%20%20parent%3A%20parentFiber%2C%20%2F%2F%20the%20parentNode%E2%80%99s%20fiber%0A%20%20child%3A%20childFiber%2C%20%2F%2F%20the%20childNode%E2%80%99s%20fiber%20if%20it%20has%20any%0A%20%20sibling%3A%20null%2C%20%2F%2F%20the%20element%20that%20is%20in%20the%20same%20tree%20level%20as%20this%20input%0A%20%20alternate%3A%20currentFiber%2C%20%2F%2F%20the%20fiber%20that%20has%20been%20rendered%20on%20the%20dom.%20Will%20be%20null%20if%20its%20on%20initial%20render%0A%20%20stateNode%3A%20document.createElement(%E2%80%9Cdiv%E2%80%9D)%2C%0A%20%20props%3A%20%7B%20children%3A%20%5B%5D%2C%20id%3A%20%22image%22%2C%20type%3A%20%22text%22%7D%2C%20%0A%20%20effectTag%3A%20PLACEMENT%2C%2F%2F%20can%20either%20be%20PLACEMENT%20%7C%20DELETION%20%7C%20UPDATe%20depending%20on%20the%20dom%20operation%20to%20be%20done%0A%20%20effects%3A%20%5B%5D%20%2F%2F%20this%20array%20will%20contain%20fibers%20of%20its%20childComponent%0A%7D%3B" message="" highlight="" provider="manual"/]

it’s just a javascript object.

Work Phases

We will have 3 work phases.

  1. The BeginWork phase will traverse the work in progress tree until it gets to the very last child as we apply the effectTag. (we’ll implement this so don’t worry about the details for now)
  2. CompleteWork phase traverses back up the tree until we get to the Fiber with no parent as we apply effects from child to parent until the parent at the top has all the effects of the tree. Effects are simply Fibers that have a tag that will inform the reconciler how to apply the Fiber to the DOM. We can have a tag for adding a node to the DOM, one for updating and another for removing the node.
  3. CommitWork will be responsible for making the DOM manipulations based on the effects array that was created in the CompleteWork phase

Now that we have a general understanding lets begin working on the individual pieces that have will be combined to form the 3 phases described above.

Enough talk lets code. First, clear out the reconciler file with the exception of the imports.

[pastacode lang="javascript" manual="import%20%7B%20updateDomProperties%20%7D%20from%20%22.%2Fdom-utils%22%3B%0Aimport%20%7B%20TEXT_ELEMENT%20%7D%20from%20%22.%2Felement%22%3B%0Aconst%20ENOUGH_TIME%20%3D%201%3B%20%2F%2F%20we%20set%20ours%20to%201%20millisecond.%0A%0Alet%20workQueue%20%3D%20%5B%5D%3B%20%2F%2F%20there%20is%20no%20work%20initially%0Alet%20nextUnitOfWork%20%3D%20null%3B%20%2F%2F%20the%20nextUnitOfWork%20is%20null%20on%20initial%20render.%0A%0A%2F%2F%20the%20schedule%20function%20heere%20can%20stand%20%0A%2F%2F%20for%20the%20scheduleUpdate%20or%20the%20%0A%2F%2F%20call%20to%20render%20%0A%2F%2F%20both%20those%20calls%20update%20the%20workQueue%20with%20a%20new%20task.%0Afunction%20schedule(task)%20%7B%0A%20%20%2F%2F%20add%20the%20task%20to%20the%20workqueue.%20It%20will%20be%20worked%20on%20later.%0A%20%20workQueue.push(task)%3B%0A%20%20%2F%2F%20request%20to%20know%20when%20the%20browser%20will%20be%20pre-occupied.%0A%20%20%2F%2F%20if%20the%20browser%20doesn't%20support%20requestIdleCallback%0A%20%20%2F%2F%20react%20will%20pollyfill%20the%20function%20but%20for%20simplicities%20sake%0A%20%20%2F%2F%20ill%20assume%20your%20running%20this%20on%20an%20ever-green%20browser.%0A%20%20requestIdleCallback(performWork)%3B%0A%7D%0A%0Afunction%20performWork(deadline)%20%7B%0A%20%20loopThroughWork(deadline)%0A%20%20if%20(nextUnitOfWork%20%7C%7C%20workQueue.length%20%3E%200)%20%7B%0A%20%20%20%20%2F%2F%20if%20theres%20more%20work%20to%20be%20done.%20get%20to%20know%20when%20the%20browser%20will%20be%20occupied%0A%20%20%20%20%2F%2F%20and%20check%20if%20we%20can%20perform%20some%20work%20with%20the%20timing%20provided.%0A%20%20%20%20requestIdleCallback(performWork)%3B%0A%20%20%7D%0A%7D%0A%0Afunction%20loopThroughWork(deadline)%20%7B%0A%20%20%20%20while%20(nextUnitOfWork%20%26%26%20deadline.timeRemaining()%20%3E%20ENOUGH_TIME)%20%7B%0A%20%20%20%20%2F**%0A%20%20%20%20%20*%20perform%20unitofwork%20on%20a%20fiber%20if%20there's%20enough%20time%20to%20spare%0A%20%20%20%20%20*%20from%20the%20browser's%20end.%0A%20%20%20%20%20*%2F%0A%20%20%20%20nextUnitOfWork%20%3D%20performUnitOfWork(nextUnitOfWork)%3B%0A%20%20%7D%0A%7D" message="" highlight="" provider="manual"/]

The schedule function simply updates the workQueue and a call to performWork is made. We don’t really need the schedule function and we’ll replace it in the end since its job will be done by a call to setState and renderit just stands as a placeholder to show you what the 2 functions will do. performWork simply loops through each item in the workQueue and this is how we beginWork

We’ll have a nextUnitOfWork and a performUnitOfWork function. The performUnitOfWork will work on the current-Fiber and return the nextUnitOfWork which will benext Fiber to be worked on.

An update to the workQueue needs to happen when we call setState or render

Let’s begin with making the setState function update the queue.

[pastacode lang="javascript" manual="import%20%7B%20updateDomProperties%20%7D%20from%20%22.%2Fdom-utils%22%3B%0Aimport%20%7B%20TEXT_ELEMENT%20%7D%20from%20%22.%2Felement%22%3B%0Aconst%20ENOUGH_TIME%20%3D%201%3B%20%2F%2F%20we%20set%20ours%20to%201%20millisecond.%0A%0Alet%20workQueue%20%3D%20%5B%5D%3B%20%2F%2F%20there%20is%20no%20work%20initially%0Alet%20nextUnitOfWork%20%3D%20null%3B%20%2F%2F%20the%20nextUnitOfWork%20is%20null%20on%20initial%20render.%0A%0A%2F%2F%20the%20schedule%20function%20heere%20can%20stand%20%0A%2F%2F%20for%20the%20scheduleUpdate%20or%20the%20%0A%2F%2F%20call%20to%20render%20%0A%2F%2F%20both%20those%20calls%20update%20the%20workQueue%20with%20a%20new%20task.%0Afunction%20schedule(task)%20%7B%0A%20%20%2F%2F%20add%20the%20task%20to%20the%20workqueue.%20It%20will%20be%20worked%20on%20later.%0A%20%20workQueue.push(task)%3B%0A%20%20%2F%2F%20request%20to%20know%20when%20the%20browser%20will%20be%20pre-occupied.%0A%20%20%2F%2F%20if%20the%20browser%20doesn't%20support%20requestIdleCallback%0A%20%20%2F%2F%20react%20will%20pollyfill%20the%20function%20but%20for%20simplicities%20sake%0A%20%20%2F%2F%20ill%20assume%20your%20running%20this%20on%20an%20ever-green%20browser.%0A%20%20requestIdleCallback(performWork)%3B%0A%7D%0A%0Afunction%20performWork(deadline)%20%7B%0A%20%20loopThroughWork(deadline)%0A%20%20if%20(nextUnitOfWork%20%7C%7C%20workQueue.length%20%3E%200)%20%7B%0A%20%20%20%20%2F%2F%20if%20theres%20more%20work%20to%20be%20done.%20get%20to%20know%20when%20the%20browser%20will%20be%20occupied%0A%20%20%20%20%2F%2F%20and%20check%20if%20we%20can%20perform%20some%20work%20with%20the%20timing%20provided.%0A%20%20%20%20requestIdleCallback(performWork)%3B%0A%20%20%7D%0A%7D%0A%0Afunction%20loopThroughWork(deadline)%20%7B%0A%20%20%20%20while%20(nextUnitOfWork%20%26%26%20deadline.timeRemaining()%20%3E%20ENOUGH_TIME)%20%7B%0A%20%20%20%20%2F**%0A%20%20%20%20%20*%20perform%20unitofwork%20on%20a%20fiber%20if%20there's%20enough%20time%20to%20spare%0A%20%20%20%20%20*%20from%20the%20browser's%20end.%0A%20%20%20%20%20*%2F%0A%20%20%20%20nextUnitOfWork%20%3D%20performUnitOfWork(nextUnitOfWork)%3B%0A%20%20%7D%0A%7D" message="" highlight="" provider="manual"/]

[pastacode lang="javascript" manual="const%20CLASS_COMPONENT%20%3D%20%22class%22%3B%0A%2F%2F%20...code%0A%0Aexport%20function%20scheduleUpdate(instance%2C%20partialState)%20%7B%0A%20%20workQueue.push(%7B%0A%20%20%20%20from%3A%20CLASS_COMPONENT%2C%20%2F%2F%20we%20know%20scheduleUpdate%20came%20from%20a%20class%20so%20we%20have%20CLASS_COMPONENT%20here.%0A%20%20%20%20instance%3A%20instance%2C%20%2F%2F%20*this*%20object%0A%20%20%20%20partialState%3A%20partialState%20%2F%2F%20this%20represents%20the%20state%20that%20needs%20to%20be%20changed%0A%20%20%7D)%3B%0A%20%20requestIdleCallback(performWork)%3B%0A%7D%0Aview%20raw" message="" highlight="" provider="manual"/]

The call to render also needs to update the workQueue

[pastacode lang="javascript" manual="const%20HOST_ROOT%20%3D%20%22root%22%3B%0Aconst%20HOST_COMPONENT%20%3D%20%22host%22%3B%0A%0A%2F%2F%20code..%0A%0Aexport%20function%20render(elements%2C%20containerDom)%20%7B%0A%20%20workQueue.push(%7B%0A%20%20%20%20from%3A%20HOST_ROOT%2C%20%2F%2F%20the%20root%2Fparent%20fiber%0A%20%20%20%20dom%3A%20containerDom%2C%20%2F%2F%20document.getElementById(%22app%22)%20just%20a%20dom%20node%20where%20this%20fiber%20will%20be%20appended%20to%20as%20a%20child%0A%20%20%20%20newProps%3A%20%7B%20children%3A%20elements%20%7D%0A%20%20%7D)%3B%0A%20%20requestIdleCallback(performWork)%3B%0A%7D" message="" highlight="" provider="manual"/]

Since the nextUnitOfWork is null on initial render, we need a function that gives us our first unit-of-work from the WorkInProgress tree.

[pastacode lang="javascript" manual="function%20performWork(deadline)%20%7B%0A%20%20if%20(!nextUnitOfWork)%20%7B%0A%20%20%20%20%2F%2F%20on%20initial%20render%20%0A%20%20%20%20%2F%2F%20or%20if%20all%20work%20is%20complete%20and%20the%20nextUnitOfWork%20is%20null%0A%20%20%20%20%2F%2Fgrab%20the%20first%20item%20on%20the%20workInProgress%20queue.%0A%20%20%20%20initialUnitOfWork()%3B%0A%20%20%7D%0A%20%20loopThroughWork(deadline)%0A%20%20if%20(nextUnitOfWork%20%7C%7C%20workQueue.length%20%3E%200)%20%7B%0A%20%20%20%20%2F%2F%20if%20theres%20more%20work%20to%20be%20done.%20get%20to%20know%20when%20the%20browser%20will%20be%20occupied%0A%20%20%20%20%2F%2F%20and%20check%20if%20we%20can%20perform%20some%20work%20with%20the%20timing%20provided.%0A%20%20%20%20requestIdleCallback(performWork)%3B%0A%20%20%7D%0A%7D%0A%0Afunction%20initialUnitOfWork()%20%7B%0A%20%20%20%2F%2Fgrab%20the%20first%20item%20in%20the%20array%0A%20%20%20%2F%2F%20its%20a%20first%20come%20first%20serve%20scenario.%0A%20%20%20const%20update%20%3D%20workQueue.shift()%3B%20%0A%20%20%0A%20%20%20%2F%2F%20if%20there%20are%20no%20updates%20pending%0A%20%20%2F%2F%20abort%20since%20there%20is%20no%20work%20to%20do.%0A%20%20if%20(!update)%20%7B%0A%20%20%20%20return%3B%0A%20%20%7D%0A%0A%20%20%2F%2F%20this%20call%20will%20apply%20if%20the%20update%20came%20from%20setState%0A%20%20%2F%2F%20we%20need%20the%20object%20passed%20in%20this.setState%20to%20the%0A%20%20%2F%2F%20partialState%20of%20the%20current%20fiber%20%0A%20%20if%20(update.partialState)%20%7B%0A%20%20%20%20update.instance.__fiber.partialState%20%3D%20update.partialState%3B%0A%20%20%7D%0A%0A%20%20const%20root%20%3D%0A%20%20%20%20update.from%20%3D%3D%3D%20HOST_ROOT%0A%20%20%20%20%20%20%3F%20update.dom._rootContainerFiber%0A%20%20%20%20%20%20%3A%20getRootNode(update.instance.__fiber)%3B%0A%0A%20%20nextUnitOfWork%20%3D%20%7B%0A%20%20%20%20tag%3A%20HOST_ROOT%2C%0A%20%20%20%20stateNode%3A%20update.dom%20%7C%7C%20root.stateNode%2C%20%2F%2F%20the%20properties%20from%20the%20update%20are%20checked%20first%20for%20existence%0A%20%20%20%20props%3A%20update.newProps%20%7C%7C%20root.props%2C%20%2F%2F%20if%20the%20update%20properties%20are%20missing%20default%20back%20to%20the%20root%20properties%0A%20%20%20%20alternate%3A%20root%0A%20%20%7D%3B%0A%7D%0A%0Afunction%20getRootNode(fiber)%20%7B%0A%20%20%2F%2F%20climb%20up%20the%20fiber%20until%20we%20reach%20to%20the%20fiber%20with%20no%20parent%0A%20%20%2F%2F%20This%20will%20give%20us%20the%20alternate%20property%20of%20each%20fiber%20if%20its%20not%0A%20%20%2F%2F%20the%20host_root%2C%20meaning%20the%20fiber%20at%20the%20very%20top%20of%20the%20tree%0A%20%20let%20node%20%3D%20fiber%3B%0A%20%20while%20(node.parent)%20%7B%0A%20%20%20%20%2F%2F%20as%20long%20as%20the%20current%20node%20has%20a%20parent%20keep%20climbing%20up%0A%20%20%20%20%2F%2F%20until%20node.parent%20is%20null.%0A%20%20%20%20node%20%3D%20node.parent%3B%0A%20%20%7D%0A%20%20return%20node%3B%0A%7D%0A" message="" highlight="" provider="manual"/]

Let’s now define our performUnitOfWork function.

This function performs work on the Fiber that has been passed to it as a parameter. It then goes ahead to work on its children and finally works on its siblings and the cycle continues.

Small visual representation of how the work is done.

[pastacode lang="javascript" manual="%2F%2F%20...%20code%0Afunction%20performUnitOfWork(wipFiber)%20%7B%0A%20%20%2F%2F%20lets%20work%20on%20the%20fiber%0A%20%20beginWork(wipFiber)%3B%0A%20%20if%20(wipFiber.child)%20%7B%0A%20%20%20%20%2F%2F%20if%20a%20child%20exists%20its%20passed%20on%20as%0A%20%20%20%20%2F%2F%20the%20nextUnitOfWork%0A%20%20%20%20return%20wipFiber.child%3B%0A%20%20%7D%0A%0A%20%20%2F%2F%20No%20child%2C%20we%20call%20completeWork%20until%20we%20find%20a%20sibling%0A%20%20let%20uow%20%3D%20wipFiber%3B%0A%20%20while%20(uow)%20%7B%0A%20%20%20%20completeWork(uow)%3B%20%2F%2F%20completework%20on%20the%20currentFiber%0A%20%20%20%20%2F%2F%20return%20the%20siblings%20of%20the%20currentFiber%20to%0A%20%20%20%20%2F%2F%20be%20the%20nextUnitOfWork%0A%20%20%20%20if%20(uow.sibling)%20%7B%0A%20%20%20%20%20%20%2F%2F%20Sibling%20needs%20to%20beginWork%0A%20%20%20%20%20%20return%20uow.sibling%3B%0A%20%20%20%20%7D%0A%20%20%20%20%2F%2F%20if%20no%20siblings%20are%20present%2C%0A%20%20%20%20%2F%2F%20lets%20climb%20up%20the%20tree%20as%20we%20call%20completeWork%0A%20%20%20%20%2F%2F%20when%20no%20parent%20is%20found%20%2F%20if%20we've%20reached%20the%20top%2C%0A%20%20%20%20%2F%2F%20this%20function%20returns%20null%20and%20thats%20how%20we%20know%20that%20we%20have%20completed%0A%20%20%20%20%2F%2F%20working%20on%20the%20work%20in%20progress%20tree.%0A%20%20%20%20uow%20%3D%20uow.parent%3B%0A%20%20%7D%0A%7D" message="" highlight="" provider="manual"/]

The idea is, go down the tree traversing the siblings and children, then come back up completing work on each level. (We go down then come back up again)

  Let’s define beginWork

[pastacode lang="javascript" manual="%2F%2F%20...code%0Afunction%20beginWork(wipFiber)%20%7B%0A%20%20if%20(wipFiber.tag%20%3D%3D%20CLASS_COMPONENT)%20%7B%0A%20%20%20%20updateClassFiber(wipFiber)%3B%0A%20%20%7D%20else%20%7B%0A%20%20%20%20updateHostFiber(wipFiber)%3B%0A%20%20%7D%0A%7D%0A%0Afunction%20updateHostFiber(wipFiber)%20%7B%0A%20%20if%20(!wipFiber.stateNode)%20%7B%0A%20%20%20%20%2F%2F%20if%20this%20is%20the%20initialRender%20and%20stateNode%20is%20null%0A%20%20%20%20%2F%2F%20create%20a%20new%20node.%0A%20%20%20%20wipFiber.stateNode%20%3D%20createDomElement(wipFiber)%3B%0A%20%20%7D%0A%20%20const%20newChildElements%20%3D%20wipFiber.props.children%3B%0A%20%20reconcileChildrenArray(wipFiber%2C%20newChildElements)%3B%0A%7D%0A%0Afunction%20updateClassFiber(wipFiber)%20%7B%0A%20%20let%20instance%20%3D%20wipFiber.stateNode%3B%0A%20%20if%20(instance%20%3D%3D%20null)%20%7B%0A%20%20%20%20%2F%2F%20if%20this%20is%20the%20initialRender%20call%20the%20constructor%0A%20%20%20%20instance%20%3D%20wipFiber.stateNode%20%3D%20createInstance(wipFiber)%3B%0A%20%20%7D%20else%20if%20(wipFiber.props%20%3D%3D%20instance.props%20%26%26%20!wipFiber.partialState)%20%7B%0A%20%20%20%20%2F%2F%20nothing%20has%20changed%20here%0A%20%20%20%20%2F%2F%20lets%20move%20to%20the%20children%0A%20%20%20%20cloneChildFibers(wipFiber)%3B%0A%20%20%20%20return%3B%0A%20%20%7D%0A%0A%20%20instance.props%20%3D%20wipFiber.props%3B%0A%20%20instance.state%20%3D%20Object.assign(%7B%7D%2C%20instance.state%2C%20wipFiber.partialState)%3B%0A%20%20wipFiber.partialState%20%3D%20null%3B%0A%0A%20%20const%20newChildElements%20%3D%20wipFiber.stateNode.render()%3B%0A%20%20reconcileChildrenArray(wipFiber%2C%20newChildElements)%3B%0A%7D%0A%0Afunction%20createInstance(fiber)%20%7B%0A%20%20%2F%2Fsimilar%20to%20the%20previous%20implementation%0A%20%20%2F%2F%20we%20instanciate%20a%20new%20object%20of%20the%20class%20provided%20in%20the%0A%20%20%2F%2F%20type%20prop%20and%20return%20the%20new%20instance%0A%20%20const%20instance%20%3D%20new%20fiber.type(fiber.props)%3B%0A%20%20instance.__fiber%20%3D%20fiber%3B%0A%20%20return%20instance%3B%0A%7D%0A%0Afunction%20createDomElement(fiber)%20%7B%0A%20%20%2F%2F%20check%20the%20type%20of%20the%20fiber%20object.%0A%20%20const%20isTextElement%20%3D%20fiber.type%20%3D%3D%3D%20TEXT_ELEMENT%3B%0A%20%20const%20dom%20%3D%20isTextElement%0A%20%20%20%20%3F%20document.createTextNode(%22%22)%0A%20%20%20%20%3A%20document.createElement(fiber.type)%3B%0A%20%20updateDomProperties(dom%2C%20%5B%5D%2C%20fiber.props)%3B%0A%20%20return%20dom%3B%0A%7D" message="" highlight="" provider="manual"/]

We perform different operations for the various Fibers. Either class or host component

Since we have reconciled the host Fibers, we also need to reconcile their children as well.

[pastacode lang="javascript" manual="%2F%2F%20..%20code%0A%0A%0Aconst%20PLACEMENT%20%3D%20%22PLACEMENT%22%3B%20%2F%2F%20this%20is%20for%20a%20child%20that%20needs%20to%20be%20added%0Aconst%20DELETION%20%3D%20%22DELETION%22%3B%20%2F%2Ffor%20a%20child%20that%20needs%20to%20be%20deleted.%0Aconst%20UPDATE%20%3D%20%22UPDATE%22%3B%20%2F%2F%20for%20a%20child%20that%20needs%20to%20be%20updated.%20refresh%20the%20props%0A%0Afunction%20createArrayOfChildren(children)%20%7B%0A%20%2F%2F%20we%20can%20pass%20children%20as%20an%20array%20now%20in%20the%20call%20to%20render%0A%20%2F**%0A%20*%20render%20()%20%7B%0A%20%20%20return%20%5B%0A%20%20%20%20%20%3Cdiv%3EFirst%3C%2Fdiv%3E%2C%0A%20%20%20%20%20%3Cdiv%3ESecond%3C%2Fdiv%3E%0A%20%20%20%5D%0A%20%7D%0A%20*%2F%0A%20%20return%20!children%20%3F%20%5B%5D%20%3A%20Array.isArray(children)%20%3F%20children%20%3A%20%5Bchildren%5D%3B%0A%7D%0A%0Afunction%20reconcileChildrenArray(wipFiber%2C%20newChildElements)%20%7B%0A%20%20const%20elements%20%3D%20createArrayOfChildren(newChildElements)%3B%0A%0A%20%20let%20index%20%3D%200%3B%0A%20%20%2F%2F%20let%20the%20oldFiber%20point%20to%20the%20fiber%20thats%20been%20rendered%20in%20the%0A%20%20%2F%2F%20dom%20if%20its%20present.%20if%20its%20initialRender%20then%20return%20null.%0A%20%20let%20oldFiber%20%3D%20wipFiber.alternate%20%3F%20wipFiber.alternate.child%20%3A%20null%3B%0A%20%20let%20newFiber%20%3D%20null%3B%0A%20%20while%20(index%20%3C%20elements.length%20%7C%7C%20oldFiber%20!%3D%20null)%20%7B%0A%20%20%20%20const%20prevFiber%20%3D%20newFiber%3B%0A%20%20%20%20%2F%2F%20we%20wither%20get%20an%20element%20or%20false%20back%20in%20this%20check.%0A%20%20%20%20const%20element%20%3D%20index%20%3C%20elements.length%20%26%26%20elements%5Bindex%5D%3B%0A%0A%20%20%20%20%2F%2F%20if%20the%20type%20of%20the%20old%20fiber%20is%20the%20same%20as%20the%20new%20fiber%0A%20%20%20%20%2F%2F%20we%20just%20need%20to%20update%20this%20fiber%0A%20%20%20%20%2F%2F%20its%20the%20same%20check%20as%20the%20one%20we%20had%20in%20the%20previous%0A%20%20%20%20%2F%2F%20reconciliation%20algorithm%0A%20%20%20%20const%20sameType%20%3D%20oldFiber%20%26%26%20element%20%26%26%20element.type%20%3D%3D%20oldFiber.type%3B%0A%0A%20%20%20%20if%20(sameType)%20%7B%0A%20%20%20%20%20%20%2F%2F%20on%20an%20update%20the%20only%20new%20thing%20that%20gets%0A%20%20%20%20%20%20%2F%2F%20changed%20is%20the%20props%20of%20the%20fiber%0A%20%20%20%20%20%20%2F%2F%20I%20should%20have%20spread%20this%20but%20for%20easier%0A%20%20%20%20%20%20%2F%2F%20understading%20and%20so%20that%20we%20understand%20where%20everything%0A%20%20%20%20%20%20%2F%2F%20goes%20and%20the%20underlying%20structure%2C%20Ill%20do%20what%20seemengly%20seems%0A%20%20%20%20%20%20%2F%2Flike%20im%20repeating%20myself.%0A%20%20%20%20%20%20newFiber%20%3D%20%7B%0A%20%20%20%20%20%20%20%20type%3A%20oldFiber.type%2C%0A%20%20%20%20%20%20%20%20tag%3A%20oldFiber.tag%2C%0A%20%20%20%20%20%20%20%20stateNode%3A%20oldFiber.stateNode%2C%0A%20%20%20%20%20%20%20%20props%3A%20element.props%2C%0A%20%20%20%20%20%20%20%20parent%3A%20wipFiber%2C%0A%20%20%20%20%20%20%20%20alternate%3A%20oldFiber%2C%0A%20%20%20%20%20%20%20%20partialState%3A%20oldFiber.partialState%2C%0A%20%20%20%20%20%20%20%20effectTag%3A%20UPDATE%0A%20%20%20%20%20%20%7D%3B%0A%20%20%20%20%7D%0A%0A%20%20%20%20if%20(element%20%26%26%20!sameType)%20%7B%0A%20%20%20%20%20%20%2F%2F%20this%20is%20when%20an%20element%20wasn't%20present%0A%20%20%20%20%20%20%2F%2F%20before%20but%20is%20now%20present.%0A%20%20%20%20%20%20newFiber%20%3D%20%7B%0A%20%20%20%20%20%20%20%20type%3A%20element.type%2C%0A%20%20%20%20%20%20%20%20tag%3A%0A%20%20%20%20%20%20%20%20%20%20typeof%20element.type%20%3D%3D%3D%20%22string%22%20%3F%20HOST_COMPONENT%20%3A%20CLASS_COMPONENT%2C%0A%20%20%20%20%20%20%20%20props%3A%20element.props%2C%0A%20%20%20%20%20%20%20%20parent%3A%20wipFiber%2C%0A%20%20%20%20%20%20%20%20effectTag%3A%20PLACEMENT%0A%20%20%20%20%20%20%7D%3B%0A%20%20%20%20%7D%0A%0A%20%20%20%20if%20(oldFiber%20%26%26%20!sameType)%20%7B%0A%20%20%20%20%20%20%2F%2F%20in%20this%20check%20%20we%20see%20its%20when%20a%20component%0A%20%20%20%20%20%20%2F%2F%20was%20present%2C%20but%20is%20now%20not%20present.%0A%20%20%20%20%20%20%2F%2F%20like%20a%20deleted%20to%20do%20list.%0A%20%20%20%20%20%20oldFiber.effectTag%20%3D%20DELETION%3B%0A%20%20%20%20%20%20wipFiber.effects%20%3D%20wipFiber.effects%20%7C%7C%20%5B%5D%3B%0A%20%20%20%20%20%20%2F%2F%20we%20need%20to%20keep%20a%20reference%20of%20what%20gets%20deleted%0A%20%20%20%20%20%20%2F%2F%20here%20we%20add%20the%20fiber%20to%20be%20deleted%20onto%20the%20effects%20array.%0A%20%20%20%20%20%20%2F%2F%20we'll%20work%20with%20the%20effects%20later%20on%20in%20the%20commit%20stages.%0A%20%20%20%20%20%20wipFiber.effects.push(oldFiber)%3B%0A%20%20%20%20%7D%0A%0A%20%20%20%20if%20(oldFiber)%20%7B%0A%20%20%20%20%20%20%2F%2F%20we%20are%20only%20interested%20in%20the%20siblings%20of%20the%0A%20%20%20%20%20%20%2F%2F%20children%20that%20are%20in%20the%20same%20level%20here%0A%20%20%20%20%20%20%2F%2F%20tree%20level%20here%0A%20%20%20%20%20%20%2F%2F%20in%20other%20terms%20we%20just%20need%20the%20siblings%20of%20the%20render%20array.%0A%20%20%20%20%20%20oldFiber%20%3D%20oldFiber.sibling%3B%0A%20%20%20%20%7D%0A%0A%20%20%20%20if%20(index%20%3D%3D%200)%20%7B%0A%20%20%20%20%20%20wipFiber.child%20%3D%20newFiber%3B%0A%20%20%20%20%7D%20else%20if%20(prevFiber%20%26%26%20element)%20%7B%0A%20%20%20%20%20%20prevFiber.sibling%20%3D%20newFiber%3B%0A%20%20%20%20%7D%0A%0A%20%20%20%20index%2B%2B%3B%0A%20%20%7D%0A%7D" message="" highlight="" provider="manual"/]

We need to keep track of the children that need to be updated, deleted or appended as new components

we made a call to cloneChildFibers, in essence, the function gives the children of the parentFiber a new parent property. The parentFiber from the work in progress tree becomes their new parent. This is to replace the currentFiber of the node that has been rendered to the DOM. Let’s define it below

[pastacode lang="javascript" manual="%2F%2F%20..%20code%0Afunction%20cloneChildFibers(parentFiber)%20%7B%0A%20%20const%20oldFiber%20%3D%20parentFiber.alternate%3B%0A%20%20%2F%2F%20if%20there%20is%20no%20child%20for%20the%20alternate%0A%20%20%2F%2F%20there's%20no%20more%20work%20to%20do%0A%20%20%2F%2F%20so%20just%20kill%20the%20execution%0A%20%20if%20(!oldFiber.child)%20%7B%0A%20%20%20%20return%3B%0A%20%20%7D%0A%0A%20%20let%20oldChild%20%3D%20oldFiber.child%3B%0A%20%20%2F%2F%20on%20initial%20render%2C%20the%20prevChild%20is%20null.%0A%20%20let%20prevChild%20%3D%20null%3B%0A%20%20%2F**%0A%20%20%20*%20below%20we%20are%20essencially%20looping%20through%20all%20the%20siblings%0A%20%20%20*%20so%20that%20can%20give%20them%20their%20new%20parent%20which%20is%20the%20workInProgress%20fiber%0A%20%20%20*%20the%20other%20properties%20are%20hard%20coded%20as%20well.%0A%20%20%20*%20I%20could%20have%20spread%20them%20but%20for%20understanding%20of%20the%0A%20%20%20*%20structure%20given%2C%20We%20are%20not%20going%20to%20spread%20them%20here.%0A%20%20%20*%2F%0A%20%20while%20(oldChild)%20%7B%0A%20%20%20%20const%20newChild%20%3D%20%7B%0A%20%20%20%20%20%20type%3A%20oldChild.type%2C%0A%20%20%20%20%20%20tag%3A%20oldChild.tag%2C%0A%20%20%20%20%20%20stateNode%3A%20oldChild.stateNode%2C%0A%20%20%20%20%20%20props%3A%20oldChild.props%2C%0A%20%20%20%20%20%20partialState%3A%20oldChild.partialState%2C%0A%20%20%20%20%20%20alternate%3A%20oldChild%2C%0A%20%20%20%20%20%20parent%3A%20parentFiber%0A%20%20%20%20%7D%3B%0A%20%20%20%20if%20(prevChild)%20%7B%0A%20%20%20%20%20%20prevChild.sibling%20%3D%20newChild%3B%0A%20%20%20%20%7D%20else%20%7B%0A%20%20%20%20%20%20parentFiber.child%20%3D%20newChild%3B%0A%20%20%20%20%7D%0A%20%20%20%20prevChild%20%3D%20newChild%3B%0A%20%20%20%20oldChild%20%3D%20oldChild.sibling%3B%0A%20%20%7D%0A%7D" message="" highlight="" provider="manual"/]

Now that we have cloned all the children and there’s nothing else left for us to do. It’s finally time to complete the work done and finally flush the changes to the DOM.

[pastacode lang="javascript" manual="%2F%2F%20...code%0A%0Alet%20pendingCommit%20%3D%20null%3B%20%2F%2F%20this%20is%20what%20will%20be%20flushed%20to%20the%20dom%0A%2F%2F%20...%20code%0A%0A%0Afunction%20completeWork(fiber)%20%7B%0A%20%20%2F%2F%20this%20function%20takes%20the%20list%20of%20effects%20of%20the%20children%20and%20appends%20them%20to%20the%20effects%20of%0A%20%20%2F%2F%20the%20parent%0A%20%20if%20(fiber.tag%20%3D%3D%20CLASS_COMPONENT)%20%7B%0A%20%20%20%20%2F%2F%20update%20the%20stateNode.__fiber%20of%20the%20%0A%20%20%20%20%2F%2F%20class%20component%20to%20the%20new%20wipFiber%20(it%20doesn't%20deserve%20this%20name%20anymore%20since%20we%20are%20done%20with%20the%20work%20we%20needed%20to%20do%20to%20it)%0A%20%20%20%20fiber.stateNode.__fiber%20%3D%20fiber%3B%0A%20%20%7D%0A%0A%20%20if%20(fiber.parent)%20%7B%0A%20%20%20%20%2F%2F%20append%20the%20fiber's%20child%20effects%20to%20the%20parent%20of%20the%20fiber%0A%20%20%20%20%2F%2F%20the%20effects%20of%20the%20childFiber%0A%20%20%20%20%2F%2F%20are%20appended%20to%20the%20fiber.effects%0A%20%20%20%20const%20childEffects%20%3D%20fiber.effects%20%7C%7C%20%5B%5D%3B%0A%20%20%20%20%2F%2F%20if%20the%20effectTag%20is%20not%20present%20of%20this%20fiber%2C%20if%20there%20are%20none%2C%0A%20%20%20%20%2F%2F%20then%20return%20an%20empty%20list%0A%20%20%20%20const%20thisEffect%20%3D%20fiber.effectTag%20!%3D%20null%20%3F%20%5Bfiber%5D%20%3A%20%5B%5D%3B%0A%20%20%20%20const%20parentEffects%20%3D%20fiber.parent.effects%20%7C%7C%20%5B%5D%3B%0A%20%20%20%20%2F%2F%20the%20new%20parent%20effects%20consists%20of%20this%20current%20fiber's%20effects%20%2B%0A%20%20%20%20%2F%2F%20effects%20of%20this%20current%20Fiber%20%2B%20the%20parent's%20own%20effects%0A%20%20%20%20fiber.parent.effects%20%3D%20parentEffects.concat(childEffects%2C%20thisEffect)%3B%0A%20%20%7D%20else%20%7B%0A%20%20%20%20%2F%2F%20if%20the%20fiber%20does%20not%20have%20a%20parent%20then%20it%20means%20we%0A%20%20%20%20%2F%2F%20are%20at%20the%20root.%20and%20ready%20to%20flush%20the%20changes%20to%20the%20dom.%0A%20%20%20%20pendingCommit%20%3D%20fiber%3B%0A%20%20%7D%0A%7D" message="" highlight="" provider="manual"/]

completeWork.

completeWork simply takes a Fiber and appends it’s own effects to the Fiber’s parent. It also takes a step further to append the effects of the Fiber’s child to the parent of the current Fiber. Once there is no parent Fiber then it means that we have reached the very top of our tree and now set pendingCommit to the Fiber with no parent.

Keep in mind these effects are what we will use to determine the kind of operation we need to apply to the Fiber on the DOM.

We now need a way to tell the performWork function to finally flush the changes to the DOM-based off of thependingCommit

[pastacode lang="javascript" manual="function%20performWork(deadline)%20%7B%0A%20%20%2F%2F%20...code%0A%20%20if%20(pendingCommit)%20%7B%0A%20%20%20%20commitAllWork(pendingCommit)%3B%0A%20%20%7D%0A%7D%0A%0Afunction%20commitAllWork(fiber)%20%7B%0A%20%20%2F%2F%20this%20fiber%20has%20all%20the%20effects%20of%20the%20entire%20tree%0A%20%20fiber.effects.forEach(f%20%3D%3E%20%7B%0A%20%20%20%20commitWork(f)%3B%0A%20%20%7D)%3B%0A%20%20%2F%2F%20the%20wipFiber%20becomes%20the%20currentFiber%0A%20%20fiber.stateNode._rootContainerFiber%20%3D%20fiber%3B%0A%20%20nextUnitOfWork%20%3D%20null%3B%20%2F%2F%20no%20work%20is%20left%20to%20be%20done%0A%20%20pendingCommit%20%3D%20null%3B%20%2F%2F%20we%20have%20just%20flushed%20the%20changes%20to%20the%20dom.%0A%7D%0A%0Afunction%20commitWork(fiber)%20%7B%0A%20%20if%20(fiber.tag%20%3D%3D%20HOST_ROOT)%20%7B%0A%20%20%20%20return%3B%0A%20%20%7D%0A%20%20let%20domParentFiber%20%3D%20fiber.parent%3B%0A%20%20while%20(domParentFiber.tag%20%3D%3D%20CLASS_COMPONENT)%20%7B%0A%20%20%20%20domParentFiber%20%3D%20domParentFiber.parent%3B%0A%20%20%7D%0A%20%20const%20domParent%20%3D%20domParentFiber.stateNode%3B%0A%20%20if%20(fiber.effectTag%20%3D%3D%20PLACEMENT%20%26%26%20fiber.tag%20%3D%3D%20HOST_COMPONENT)%20%7B%0A%20%20%20%20%2F%2F%20add%20the%20new%20element%20to%20the%20dom%0A%20%20%20%20domParent.appendChild(fiber.stateNode)%3B%0A%20%20%7D%20else%20if%20(fiber.effectTag%20%3D%3D%20UPDATE)%20%7B%0A%20%20%20%20%2F%2F%20update%20the%20dom%20properties%20of%20the%20element.%0A%20%20%20%20updateDomProperties(fiber.stateNode%2C%20fiber.alternate.props%2C%20fiber.props)%3B%0A%20%20%7D%20else%20if%20(fiber.effectTag%20%3D%3D%20DELETION)%20%7B%0A%20%20%20%20%2F%2F%20remove%20the%20node%20from%20the%20DOM%20its%20not%20needed%0A%20%20%20%20commitDeletion(fiber%2C%20domParent)%3B%0A%20%20%7D%0A%7D%0A%0Afunction%20commitDeletion(fiber%2C%20domParent)%20%7B%0A%20%20%2F%2F%20this%20function%0A%20%20%2F%2F%20removes%20the%20siblings%20of%20the%20current%20fiber%0A%20%20%2F%2F%20if%20a%20sibling%20is%20not%20present%20jump%20back%20to%20the%20parent%0A%20%20%2F%2F%20of%20the%20fiber.%20This%20is%20if%20the%20node%20is%20not%20equal%20to%20the%20fiber%0A%20%20let%20node%20%3D%20fiber%3B%0A%20%20while%20(true)%20%7B%0A%20%20%20%20if%20(node.tag%20%3D%3D%20CLASS_COMPONENT)%20%7B%0A%20%20%20%20%20%20%2F%2F%20check%20the%20child%20of%20the%20class%20component.%0A%20%20%20%20%20%20%2F%2F%20then%20loop%20back.%0A%20%20%20%20%20%20node%20%3D%20node.child%3B%0A%20%20%20%20%20%20continue%3B%0A%20%20%20%20%7D%0A%20%20%20%20domParent.removeChild(node.stateNode)%3B%0A%20%20%20%20while%20(node%20!%3D%20fiber%20%26%26%20!node.sibling)%20%7B%0A%20%20%20%20%20%20%2F%2F%20if%20there%20are%20no%20siblings%20jump%20back%20up%20to%0A%20%20%20%20%20%20%2F%2F%20to%20the%20node's%20parent.%0A%20%20%20%20%20%20node%20%3D%20node.parent%3B%0A%20%20%20%20%7D%0A%20%20%20%20if%20(node%20%3D%3D%20fiber)%20%7B%0A%20%20%20%20%20%20return%3B%0A%20%20%20%20%7D%0A%20%20%20%20node%20%3D%20node.sibling%3B%0A%20%20%7D%0A%7D" message="" highlight="" provider="manual"/]

commitAllWork runs through all effects calling commitWork on each one. Commit work then either make a call to commitDeletionin case of deletion or adds the stateNode to the DOM in case of placement or simply updates the DOM properties in case of an update. This is all determined by the effectTagon the fiber.

We are done now 🎉.

It’s testing time.

Build the module

$ yarn run build:all

Open up an example file you have in your example folder and see everything still works.

For a full-blown app

Follow along with this repository’s setup to see how you can make a demo app with Webpack and babel using your React clone.

Don’t use this module in production either. :)

Main Take-Aways

The core React team went a long way to optimize the React codebase to be as fast for your apps as possible.

We can give React an easy time by

1. Offloading expensive tasks to web workers.

This frees up the main thread to handle things like animations and makes your apps even more responsive.

2. Have fewer divs and wrappers.

Each wrapper is a Fiber, the more they are the more time it to reconcile your updates and flush the changes to the DOM. Use fragments to avoid having many unnecessary nodes and fibers that have to be traversed for little to no reason.

Fragments

We couldn’t fit adding fragments into this tutorial but what React does, when it identifies a fragment in the tree, it skips over it and goes to its children. Here is an example in the React codebase of how React deals with updating fragments

3. Use Hooks.

Hooks help reduce the heavy nesting of your components that are introduced by higher-order components and render props. If there is too much nesting your apps become slower.

If you follow any of the above 3 steps /all of them, React and the main thread will not have to do as much work to give your users a seamless user experience.

Hooks help reduce the heavy nesting of your components that are introduced by higher-order components and render props. If there is too much nesting your apps become slower.

If you follow any of the above 3 steps /all of them, React and the main thread will not have to do as much work to give your users a seamless user experience.

References:

This video provides a basic understanding of modern React

- React Fibre Architecture

- React Fibre Resources

- React Fibre Deep Dive With Dan Abramov

- Making Sense of React Hooks

Related posts

The latest articles from Andela.

Visit our blog

Customer-obsessed? 4 Steps to improve your culture

If you get your team's culture right, you can create processes that will allow you to operationalize useful new technologies. Check out our 4 steps to transform your company culture.

How to Build a RAG-Powered LLM Chat App with ChromaDB and Python

Harness the power of retrieval augmented generation (RAG) and large language models (LLMs) to create a generative AI app. Andela community member Oladimeji Sowole explains how.

Navigating the future of work with generative AI and stellar UX design

In this Writer's Room blog, Carlos Tay discusses why ethical AI and user-centric design are essential in shaping a future where technology amplifies human potential.

We have a 96%+
talent match success rate.

The Andela Talent Operating Platform provides transparency to talent profiles and assessment before hiring. AI-driven algorithms match the right talent for the job.