It’s been five years since was introduced to the frontend community. Ever since it’s release, it has opened new avenues to express UI code. With React and it’s associated ecosystem, the community has constantly been working towards solving the shortcomings of client side scripting and one such effort was React Fiber which enabled us application developers to simply declare what our code looks like and how it should behave to changes in data, while behind the scenes it would compute the necessary changes to the UI. And while at it, it would in fact compute them in small time slices instead doing it all at once holding up the JS thread. React Little is know, apparently ( ), about the package that separates the computation from the scheduling of updates itself — let us write our own custom renderers. In fact, react-reconciler, the package in question, has opened whole new possibilities. Attempts have been made to ensure React code can run in , , . https://twitter.com/dan_abramov/status/1009246589473390592 firmata-based hardware pdfs QML, Regl, Framer Animations Today let’s target the DOM itself and write our own tiny react-dom renderer. If you take it seriously enough* maybe you can replace react-dom with it in you applications and see gains in bundle sizes and perf. Lets dive in. First off, lets bootstrap a React project using Create React App npx create-react-app my-react-dom-project # Or if you use yarnyarn create react-app my-react-dom-project Let’s modify the root App component to something simpler, for reasons that will become apparent soon. Run of the mill React code right? If you are not familiar with anything here, it’s recommended that . Also, please ignore the perf concerns for now in the interest of legibility. we get our React basics stronger Next, create an empty file for our custom dom renderer and call it renderer.js. (Please ignore the name of the gist files) And import this instead of the react-dom in index.js Error, right? So what do we know for sure that we need from our renderer — a render method? A method that takes our React elements, the container DOM node and a callback, right? We hand React our elements, the container node, a callback and React does it magic and runs the callback to signal it has done its work. With , we can control some of that magic. react-reconciler Good news is that the official ReactDOM itself uses the react-reconciler package. We can take hints from there. We find that ReactReconciler (the function exported by the package) Needs a config. Needs a internal container data to render into structure Run with our elements, the internal form of container and the callback we intended to be run after rendering. updateContainer So let’s give it a shot. And we have the following error. After all our `hostConfig` is an empty object! Note that all references to “host”, in code and in prose, refer the the environment the React code will be running. It’s not secret that React runs on mobile (Android/iOS) — React just needs a JS environment that can render UI updates. In our project, the host is the DOM itself. First things first — what is the ? Internally React needs a way to keep track of time for things like figuring if the piece of computation has ‘expired’ or has overstepped the time allotted. Check out for a deeper dive into the topic. But then, one wonders “Can’t React use something like ?” It can! But host environments could provide better ways of tracking time. Like ? All host enviroments may not be identical with regard to this. Anyways, let’s use for now and move on. now Lin Clark’s talk on React Fiber Date.now Performance.now Date.now A good way to figure out what goes in the would to look at (at the time of writing). We’ll find that we need the following. hostConfig ReactDOMHostConfig.js Simply adding a log statement in each of them and running the app again gives us the following. **getRootHostContext** and **getChildHostContext** This is bit that requires a bit of reading and poking around. Comparing renderers on the official repo, we can safely conclude this: React Reconciler provides and as ways to share some context among the other config functions. More on this, hopefully, in a follow up post. For now, let’s return empty objects. getRootHostContext getChildHostContext **shouldSetTextContent** Some hosts lets you set text content on the host element, while others may mandate creation of a new element. For now we simply return false. **createTextInstance** Let’s log all the arguments received in the function. Here’s what they are, => The initial value of 0 this.state.count => The DOM container <div id="root"></div> {tag: 6, key: null, type: null, stateNode: null, return: FiberNode, …}` => The Fiber associated. We don’t have t worry about this. Most of the time, we just need to pass it on to reconciler. It’s considered to be opaque. In our simple renderer, we don’t even have to do that. React calls to create text nodes in the host environment. So just return createTextInstance document.createTextNode(text) createTextInstance(text,rootContainerInstance,hostContext,internalInstanceHandle) { return document.createTextNode(text); } **createInstance** Again on logging all the arguments we see => The type of DOM node. Eg div, span, p etc. type props => Props passed. Since we never passed anything to the div in our app, only children are passed. => The root container <div id="root"></div> {…} => The fiber node Similar to , let’s just return a div node i.e. => createTextNode document.createElement(type) createInstance(type,props,rootContainerInstance,hostContext,internalInstanceHandle) {return document.createElement(type);}, **appendInitialChild** We see the parent DOM node, with no attributes or properties set, and its respective child node. It only makes sense to append the child to parent using our host’s api, in our case .appendChild() appendInitialChild(parentInstance, child) {parentInstance.appendChild(child)} **finalizeInitialChildren** It’s arguments : from in our App.js domElement <div></div> <div>{this.state.count}</div> : div type from and it’s initial value 0 props { children: 0 } {this.state.count} rootContainerInstance hostContext If we take a closer look at the logs, we find that => => => happens for every element declared in App.js — shouldSetTextContent createTextInstance createInstance finalizeInitialChildren <div>{this.state.count}</div> <button onClick={onClickHandler}>Increment</button> Parent div: <div> … </div> The reconciler is trying to create nodes in the host enviroment, in our case the DOM! **finalizeInitializeChildren** This receives a DOM node, type, props and the root container in the host. If we take hint from the name of this function (and of course consult other reconcilers :P ), we the sub trees created for us by React before it gets pushed to the DOM. For example, This represents <div>{this.state.count}</div> And this, <button onClick={onClickHandler}>Increment</button Notice how there are no classes on the elements. can be used to apply classes, event handlers and other attributes and properties. Since, in our simple app, all we really need are classes (html classes) and event handlers, lets apply them. If you wanted to handle other attributes and properties, this would be the place. finalizeInitialChildren and prepareForCommit resetAfterCommit Let leave these a noop for now. But otherwise, if you think before the reconciler enters the commit phase, you need to get anything done, do it in prepareForCommit. Likewise, the clean thereafter can be done in resetAfterCommit appendChildToContainer As the name suggest, just append our prepared DOM tree. appendChildToContainer(container, child) { container.appendChild(child)}, Voila! Our first render using API! react-reconciler Click on Increment a couple of times. UI doesn’t update. A few methods in the Reconciler config get called. We have already seen resetAfterCommit. Its and we are after. Although, we don’t see it’s best we look at it now because it works very closely with like prepareUpdate commitTextUpdate commitUpdate prepareUpdate commitTextUpdate. Remember how React initially took the world by storm with this thing called the “Diffing Algorithm”. Well, we can write it ourselves now! Before we do that, let’s keep in mind — React, the core library, is now just a UI updates scheduler. It’s we, as custom renderer authors, that decide that diffing actually means. For DOM, we look for Element properties and attributes. For a different host, it could be anything. Let’s play around a bit. Since we define the reconciliation (the diff’ing), we also get to choose what data structure holds the diff’ed changes, and it is this data structure that gets passed around — returned by us from prepareUpdate, passed on to . is quite simple — there’s no need for any data structure passing around. All we really need is the old text and the new updated and see if how the difference needs to be handled. In most simple cases, you could simply assign the new text to the DOM element. commitUpdate commitTextUpdate commitTextUpdate(textInstance, oldText, newText) { textInstance.nodeValue = newText;}, Let’s get our app to work for now (especially for those of us who are looking for a sense of accomplishment) and revisit and later. commitUpdate prepareUpdate Let’s get back to and . prepareUpdate commitUpdate We’ll need to modify our App a bit — it’s too simple currently to trigger and prepareUpdate commitUpdate. Let’s say we want to display the counter in red once it exceeds a threshold of say 10. Our App.js must be updated with the following line <div className={ this.state.count > 5 ? "counter red": "counter" }>{this.state.count}</div> And lets assume out css file (App.css) has some styles to change the color of the text. .red {color: red;} Save, reload and start incrementing the counter. Notice how and get called three times each. That’s one each for the , inner and the button. You can quickly verify that by temporarily adding a random markup like prepareUpdate commitUpdate div.root div <p><span>some text</span></p> Let’s try to diff our DOM! Let’s study all its arguments. prepareUpdate(domElement,type,oldProps,newProps,rootContainerInstance,hostContext) {console.log('prepareUpdate', oldProps, newProps, rootContainerInstance);return [ null ];}, prepareUpdate being run for each react element on incrementing As increment was clicked, children updated from 0 to 1. Clicking on Increment for > 5 times we see, At the 6th click, className changed from “counter” to “counter red”. Another prop changed. In an ideal world, if DOM data structures in JS land had simple direct mapping to C++ data structures beneath the hood, we could blindly go and run a update operation on each of these props. But we work within constraints! DOM is bound to marshal structures back and forth. . So let’s compute the least amount of necessary updates we need to make to the DOM. The diff’ing is a necessary hack and not a feature for writing functional declarative UI components And apply those updates in commitUpdate Note that we are handling event listeners (which are also passed as props) differently. And for simplicity we ensure only one event listener is present at all times. And there we go! The complete renderer.js Hope you enjoyed this article and will continue tinkering around react-reconciler! Github repo: https://github.com/prometheansacrifice/my-react-dom