January 23, 2019 • ☕️☕️☕️ 13 min read
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.
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.
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.
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"]undefined
"plugins": ["@babel/plugin-syntax-jsx"undefined "@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.
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.
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!
Back to Blog list • Edit on GitHub • Discuss on Twitter
Personal blog by Anand Raj.
I learn by breaking stuff (mostly code).