KReact: Building your own React & Virtual DOM
Tags: react, preact, rollup, virtualdom
Published: 2021-10-24 16:56:53

---
title: KReact: Building your own React & Virtual DOM
published: true
description: Let's learn React by building react within 100 lines of code 🏂🏻
tags: react, preact, rollup, virtualdom
//cover_image: https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8glwjk9ia8zko2fnliho.png
---

![](https://studio.uxpincdn.com/studio/wp-content/uploads/2021/06/What-is-a-component-library.png.webp)

In this blog we will build a library similar to react and understand how react works internally. This blog is written in a way that even if you don't know react or want to learn the internal working of react, you would definitely find it useful. 

Link to the GitHub Repository: [https://github.com/git-kamesh/kReact](https://github.com/git-kamesh/kReact)


## Reach me
- 💬 Ask me about Frontend and Backend Technologies 
- 📫 How to reach me: [Twitter @kamesh_koops](https://twitter.com/kamesh_koops)

## [REQ] Requirements & Use-cases : What we want to build?
- **[COMPONENT]** Able to split complicated UI's into smaller reuseable components (Button, DropDown, ImageSlider, etc ).
- **[JSX]** Able to write HTML template in JavaScript itself. Lets call this `JSX`.
- **[LOGIC]** JSX should allows to embed JavaScript expressions and logically controlled .
- **[PROPS]** Able to pass data/information to component from outside, lets call this `props`.
- **[STATE]** Component can have its own data/information without passing to it from outside, lets call this `state`.
- **[RE-RENDER]** Whenever `props` or `state` gets modified, the changes should be reflected to the UI automatically. Lets call this `re-render`.
- **[PREVENT]** Should be explicitly able to prevent `re-render` whenever required.
- **[LIFE-CYCLE]** Know components life-cycle events like,
    - [1] before component mounted into DOM (birth of component)
    - [2] after component mounted into DOM
    - [3] component's props gets changed
    - [4] before component is updated
    - [5] after component is updated
    - [6] before component is unmounted from the DOM (death of component)
- **[DOM-OPERATION]** Should handle DOM operations itself.
- **[DIFF]** On re-render should find difference between the old DOM and new Virtual DOM, and only update that part to DOM. Lets call this `patch`. 

## Lets understand
 - Before jumping, we have to understand few things/concepts like React, DOM, Virtual DOM, JSX, Components, State, JSX Transpiler, Reconsiliation.
- Don't know? don't worry we will see it here itself.

## React
- React is a component based UI library.
- Component can be anything like Button, DropDown, ImageSlider, PasswordInput, etc.
- Here, components are building blocks of UI and will respond to data change.
- Components allows reusability thus providing development speed, consistancy, seperation of concern, easy to maintain and unit testable.
- Also alows both,
    - Building an entire application (or)
    - Part of an application/Feature. 

## DOM
- DOM stands for Document Object Model
- Its an object representation of the parsed HTML document
- We could update the dom through DOM APIs (e.g: )
- When an DOM object is updated, browser run two expensive operations
    - **Reflow** - Calculates dimension and position of every element and its children.
    - **Repaint** - Determines visual changes (like color, opacity, visibility) and applies them.

## Virtual DOM
- Virtual DOM is nothing but an lightweight in-memory javascript object representation of the actual DOM.
- It basically mimics as an actual DOM.

## JSX
```js 
const element = <h1 className="clrR">Hello, world!</h1>;
```
- The above tag syntax is neither a string nor HTML. It is JSX.
- JSX stands for JavaScript XML. It is used to define our virtual DOM.
- Just like HTML used for buildinng actual DOM, JSX is used for building virtual DOM.
- JSX in most simple word is how React allows us to write HTML in JavaScript.
- JSX is a syntax extension for JavaScript and it is not valid JavaScript, web browsers cant read it directly.
- So, if JavaScript files contains JSX, that that file will have to be transpiled. That means that before the file gets to the web browser, a JSX compiler will translate any JSX into regular JavaScript.
- After compilation, JSX expressions become regular JavaScript function calls and evaluate to JavaScript objects.
- The above JSX example will be compiled smiliar to below.
```js
React.createElement('h1', { className: 'clrR'}, 'Hello, world!');
```

## Components
![Component Fn](https://miro.medium.com/max/912/1*aIusNUvSsON9GRG6ZdqMwA.png)

## Reconsiliation
- Whenever component's state or props gets updated, the component gets re-rendered and builds a new virtual DOM.
- Then react runs the diffing algorithm to calculate what changes should be applied to real DOM. This process is know as reconsiliation.

## Terms to understand
- **Rendering**: Process of converting virtual dom into dom
- **Mounting**: Process of injecting rendered dom into target dom
- **Patching**: Process of comparing the virtual dom and actual dom, updating the nodes which are changed

## Theories over lets play with code 😍
![](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8glwjk9ia8zko2fnliho.png)
### Rendering Logic
```
render(vnode, parent):
      IF vnode IS class component:
         CREATE NEW INSTANCE FOR component --> componentInstance
         GENERATE component VNODE BY INVOKING componentInstance.render() --> VNode
         RENDER VNODE BY PASSING VNODE INTO render FUNCTION --> DOMNode
     ELSE IF vnode IS function component:
         GENERATE VNODE BY EXECUTING vnode --> VNODE
         RENDER VNODE BY PASSING VNODE INTO render FUNCTION --> DOMNode
     ELSE IF vnode IS OBJECT:
         CONVERT vnode OBJECT INTO DOMNode
         RECURSIVELY APPLY render FUNCTION on vnode children
         ASSIGN PROPS AS DOMNode attributes 
         MOUNT DOMNode ON parent
     RETURN DOMNode
```

### Patching Logic
```
patch(dom, vnode, parent):
     IF dom AND vnode DIFFED:
         RENDER vnode --> DOMNode
         REPLACE dom WITH DOMNode
```

### Full source code
Link to the GitHub Repository: [https://github.com/git-kamesh/kReact](https://github.com/git-kamesh/kReact)

Follow me on twitter: [@kamesh\_koops](https://twitter.com/kamesh_koops)

```js
export class Component {
    constructor( props = {}) {
        this.props = props;
        this.state = null;
    }

    setState(nextState) {
        const isCompat = isObject(this.state) && isObject(nextState);
        const commitState = ()=> this.state = isCompat? Object.assign({}, this.state, nextState) : nextState;
        const prevState = isObject(this.state)? Object.assign({}, this.state) : this.state;

        if( runHook(this, 'shouldComponentUpdate') && this.base ) {
            runHook(this, 'componentWillUpdate', this.props, nextState);
            commitState();
            patch(this.base, this.render());
            runHook(this, 'componentDidUpdate', this.props, prevState);
        } else commitState();
    }

    static render(vnode, parent) {
        if( isClassComponent(vnode) ) {
            let instance = new vnode.type( combineChildrenWithProps( vnode ) );
            runHook(instance, 'componentWillMount');
            instance.base = render( instance.render(), parent);
            instance.base.instance = instance;
            runHook(instance, 'componentDidMount');
            return instance.base;
        } else return render( vnode.type(combineChildrenWithProps( vnode )), parent );
    }

    static patch(dom, vnode, parent=dom.parentNode) {
        if (dom.instance && dom.instance.constructor == vnode.type) {
            runHook(dom.instance, 'componentWillReceiveProps', combineChildrenWithProps( vnode ) );
            dom.instance.props = combineChildrenWithProps( vnode );
            return patch(dom, dom.instance.render(), parent);
        } else if ( isClassComponent(vnode.type) ) {
            const newdom = Component.render(vnode, parent);
            return parent ? (replace(newdom, dom, parent) && newdom) : (newdom);
        } else if ( !isClassComponent(vnode.type) ) return patch(dom, vnode.type( combineChildrenWithProps( vnode ) ), parent);
    }
}

export const createElement = (type, props, ...children ) => ({ type, props: props || {}, children });

export function render(vnode, parent) {
    if( isObject(vnode) ) {
        let dom = isFunction(vnode.type) ? Component.render(vnode, parent) : document.createElement( vnode.type );
        vnode.children.flat(1).map((child)=> render(child, dom));
        !isFunction(vnode.type) && Object.keys(vnode.props).map((key)=> setAttribute(dom, key, vnode.props[key]));
        return mount( dom, parent );
    } else return mount( document.createTextNode(vnode || ''), parent );
}

function patch(dom, vnode, parent=dom.parentNode) {
    if( isObject(vnode) ) {
        if( isTextNode(dom) ) return replace( render(vnode, parent), dom, parent );
        else if( isFunction(vnode.type) ) return Component.patch( dom, vnode, parent);
        else {
            let dom_map = Array.from(dom.childNodes) // Build a key value map to identify dom-node to its equivalent vnode
                .reduce((prev, node, idx)=> ({...prev, [node._idx || `__${idx}`]: node}), {});

            vnode.children.flat(1).map((child, idx)=> {
                let key = (child.props && child.props.key) || `__${idx}`;
                mount( dom_map[key]? patch(dom_map[key], child, dom) : render(child, dom) );
                delete dom_map[key]; // marks dom-vnode pair available by removing from map
            });

            Object.values(dom_map).forEach(element => { // Unmount DOM nodes which are missing in the latest vnodes
                runHook( element.instance, 'componentWillUnmount');
                element.remove();
            });

            !isFunction(vnode.type) && Object.keys(vnode.props).map((key)=> setAttribute(dom, key, vnode.props[key]));
        }
    }
    else if( isTextNode(dom) && dom.textContent != vnode ) return replace( render(vnode, parent), dom, parent );
}

function setAttribute(dom, key, value) {
    if( key.startsWith('on') && isFunction(value) ) delegateEvent(dom, key, value);
    else if( key == 'ref' && isFunction( value ) ) value( dom );
    else if( ['checked', 'value', 'className', 'key'].includes(key) ) dom[key=='key'? '_idx' :key] = value;
    else dom.setAttribute(key, value);
}

// Utils
const isFunction = ( node ) => typeof node == 'function';
const isObject = ( node ) => typeof node  == 'object';
const isTextNode = ( node ) => node.nodeType == 3;
const replace = (el, dom, parent)=> (parent && parent.replaceChild(el, dom) && el);
const mount = (el, parent)=> parent? parent.appendChild( el ) : el;
const isClassComponent = ( node ) => Component.isPrototypeOf( node.type );
const runHook = (instance, hook, ...args) => isFunction(instance && instance[hook]) ? instance[hook]( ...args) : true;
const delegateEvent = (dom, event, handler)=> {
    event = event.slice(2).toLowerCase();
    dom._evnt = dom._evnt || {};
    dom.removeEventListener(event, dom._evnt[ event ]);
    dom.addEventListener(event, dom._evnt[ event ] = handler);
}
const combineChildrenWithProps = ({ props, children })=> Object.assign({}, props, { children });
```

## Reach me
- 💬 Ask me about Frontend and Backend Technologies 
- 📫 How to reach me: [Twitter @kamesh_koops](https://twitter.com/kamesh_koops)