anamritraj.tech

Let's create ReactJS from scratch- Part 2

January 23, 2019 • ☕️☕️☕️ 13 min read

Recap

In this series of posts, we are creating major react functionalities from scratch. So far we have covered the render function. You can read more about it here: Let’s create ReactJS from scratch- Part 1.

Introducing JSX

Okay, so we got till this point where we have created our own render function which takes MyReact elements as arguments and renders them to DOM. While this is cool to render a link inside a div, if you have to render anything more than that, say navigation, it becomes annoying to write our HTML tags as javascript objects.

Let’s see an example,

const myElement = {
  type: 'div',
  props: {
    children: [
      {
        type: 'ul',
        props: {
          children: [
            {
              type: 'li',
              props: {
                children: [
                  {
                    type: 'TEXT_ELEMENT',
                    props: {
                      nodeValue: 'I want to use JSX',
                    },
                  },
                ],
              },
            },
            {
              type: 'li',
              props: {
                children: [
                  {
                    type: 'TEXT_ELEMENT',
                    props: {
                      nodeValue: "Its only second li and I'm annoyed!",
                    },
                  },
                ],
              },
            },
          ],
        },
      },
    ],
  },
}

The above lines will render this

<div>
  <ul>
    <li>I want to use JSX</li>
    <li>Its only second li and I'm annoyed!</li>
  </ul>
</div>

Introducing JSX! JSX is just syntactic sugar. Using a JSX transpiler you can convert your HTML looking syntax into elements which can be understood by javascript and rendered correctly by the browser.

As a JSX transpiler, you can use Babel. It has a great open-source community and easy to use CLI tool.

If you are working with React you are most probably using it. But JSX is not actually tied to react it can be used without react and is not very tough to understand and use.

If you want to learn more about JSX check out this article, Draft: JSX Specification and also JSX In Depth react documentation.

So in case of the above example, you can write something like below,

/** @jsx createElement */
const myElement = (
  <div>
    <ul>
      <li>I want to use JSX</li>
      <li>Its only second li and I'm annoyed!</li>
    </ul>
  </div>
)

If you are familiar with javascript you would be guessing if the above is a valid javascript: its not.

The first line is a comment which babel understands, the name createElement is the name of the function which babel is going to call for each tag it encounters. It is known as pragma in babel configuration.

Babel will transpile the JSX code into a bunch of function calls like this

const someJsx = (
  createElement(
  "div",
  { "class": "nav" },
  createElement(
    "ul",
    { "class": "nav-links" },
    createElement(
      "li",
      null,
      "Home"
    ),
    createElement(
      "li",
      null,
      "Profile"
    ),
    createElement(
      "li",
      null,
      "Logout"
    )
  )
);
)

So all we need to do now is implement createElement function. The first argument of the function is type of the element. The second element is props and the following elements are children. If you remember our implementation of the render function from here, you will remember that the render function takes the element in the form,

const myElement = {
  type: 'div',
  props: {
    ..
    children: []
  }
}

So what we need to do in createElement function is to move the third argument which contains the children under the props and return the resulting object which can be consumed by our MyReact.render function.

If I am not making sense there, let’s create the createElement function and let the code do the talking. Its similar to the React.createElement function. Check out this page to learn more.

The createElement function

I would just put it out there and explain the code.

function createElement(type, config, ...args) {
  const props = Object.assign({}, config)
  const hasChildren = args.length > 0

  props.children = hasChildren ? [].concat(...args) : []

  return { type, props }
}

The three arguments are provided to the function by the transpiler (which is babel in our case). The third argument will contain all of the children of the current node. We are simply assigning the props to the props object, children to props.children object. Then return the object, this is something our MyReact.render function can work with!

Well, that was pretty simple. But our render function MyReact.render expects the text elements to have a type of TEXT_ELEMENT and also a nodeValue prop. So let’s do that!

function createElement(type, config, ...args) {
  const props = Object.assign({}, config)
  const hasChildren = args.length > 0

  const allChildren = hasChildren ? [].concat(...args) : []
  // Filter all the children which are null,
  // we don't need to render them
  props.children = allChildren
    .filter(child => child !== null && child !== false)
    .map(child => (child instanceof Object ? child : createTextElement(child)))
  // For text nodes, child above is a string
  return { type, props }
}

// This function handles the creation of text nodes
function createTextElement(value) {
  return {
    type: 'TEXT_ELEMENT',
    props: {
      nodeValue: value,
    },
  }
}

Now our createElement function is complete and ready to use. A couple of things before you try to use this to create a web app. Since we are using JSX which is not a part of standard javascript, we need to transpile this code before we can use this. I am going to use Babel for transpiling the code into something which the browser can understand.

You can check out this REPL to see how babel converts the code into a bunch of createElement calls.

If you just want to see how this is used in a real-world application, I made a codepen demo to show the usage.

Making it work in the browser

The above codepen demo works since we are using babel as a preprocessor and it includes a bunch of plugins with it. If you want to make it work in your local machine you would have to include a couple of packages to transpile your JSX into createElement function calls.

We’ll start of by adding a bunch of npm packages,

npm i --save-dev @babel/cli @babel/core @babel/plugin-syntax-jsx @babel/plugin-transform-react-jsx @babel/preset-env
  • @babel/core is the core babel package

  • @babel/cli allows us to use babel as a CLI tool

  • @babel/plugin-syntax-jsx helps babel to understand the JSX syntax as babel does not support it out of the box

  • @babel/plugin-transform-react-jsx this is the main package which transpiles JSX into function calls. By default this uses React.createElement as the pragma function. You can read more about it here.

  • @babel/preset-env is responsible for allowing us to use the latest JavaScript without needing to micromanage syntax transforms (and optionally, browser polyfills) that are needed by the browser. This is just a collection of plugins.

Now one final thing we need to do is add a .babelrc file which is just to tell babel which presets and plugins (which we installed above) to use. Let’s define one.

{
  "presets": ["@babel/preset-env"],
  "plugins": ["@babel/plugin-syntax-jsx", "@babel/plugin-transform-react-jsx"]
}

We are almost done, now all we need to do is run the babel-cli to compile our file containing JSX code into a which the browser can understand.

babel ./src/main.js -o ./dist/bundle.js

-o flag tells babel to output the file as a specified name in a folder.

Code & Demo

The entire code for this is available on Github.

You can check out this REPL to see how babel converts the code into a bunch of MyReact.createElement calls.

I also made a codepen demo to demonstrate how you would use this in a real-world application.

Endnotes

This is the second post in the series where we are creating a clone of ReactJs! As always, the entire code for this is available on Github.

If you see any errors then please let me know! I am also human and don’t know everything. Thanks for reading! :)

I would love to know what features would you want me to implement next. Though I have only started, I plan to make this as close to original React as possible. It may never be production ready but I will learn a lot.

See you next time!


Edit on GitHubDiscuss on Twitter


anamritraj.tech

Anand Raj

Personal blog by Anand Raj.
I learn by breaking stuff (mostly code).