You're using JSX every days without a clue how React does his magic ? Ever wondered why we do have to wrap our elements in a parent ? (JSX expressions must have one parent element. 🤯🤯🤯)
Well, this article is for you. I'll do my very best to explain it to you, as I understood it. Keep in mind that's nobody is perfect, and if there's any mistake made, feel free to discuss it on Twitter, we're all learning everyday :D.
How does our JSX work ?
First things first, we have to make sure that you actually know how to insert new elements into your HTML with JavaScript. If you already know that, feel free to skip, if you don't, well ... Keep reading.
In a regular HTML/JS website, here's how you would do :
<body>
<div id="root"></div>
</body>
<script>
// You first get the targetted element
const parent = document.getElementById("root");
// Then you create the new one
const newChildren = document.createElement("div");
// And you append it to the parent it belong to
parent.appendChild(newChildren);
</script>
Pretty straightforward, right ? But you noticed that it does create an empty element, you probably want to add at least some text, and even some attributes such as an id.
<body>
<div id="root"></div>
</body>
<script>
const parent = document.getElementById("root");
const newChildren = document.createElement("div");
newChildren.setAttribute("id", "children");
newChildren.innerHTML = "Hello World !";
parent.appendChild(newChildren);
</script>
Your HTML page would now render a div, with an id of 'children', containing the 'Hello World' text, and so on for any other elements you want to create (you could write functions to help you out, but that's not the point or this article). It can become a mess really quickly, right ?
You would have to handle every attributes you want to add, every listeners, etc. You get the idea.
Now, how does React work ?
React does expose to use 2 libraries for the web developement : React and ReactDOM. Let's say you do have initialized your React project from create-react-app and it's running properly. Ultimately, once you have removed everything that is not necessery, you have a code looking like this :
import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById("root")
);
Let's get rid of the abstraction for now, and remove the JSX syntax, we'll come back on it later.
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
ReactDOM.render(
/* Insert your elements here */ ,
document.getElementById('root')
);
This function is the entry point of your React App. You are telling React to get a div with the id of 'root', and to render inside of it everything you'll pass as the first argument.
Now, how do we actually create elements ? This render function won't accept anything that is not a valid React element. Let's get into it with the raw React API we imported.
const element = React.createElement("div", null, "Hello World");
ReactDOM.render(element, document.getElementById("root"));
The create element function take 3 arguments :
- The type of the HTML element you want to create (div, span, input...)
- Some props that I'll explain juste after. For now the value is null as we don't want any
- And the children which is basically anything that will be inserted inside this new element created.
Now, what if we want to give an id to this element ?
const element = React.createElement("div", { id: "children" }, "Hello World");
This is where the second argument is used. It does accept an object of properties that will be applies to your element, here we added an id, but you could do it for a class, or some specific attributes for your tag. Even on onclick event !
const element = React.createElement(
"div",
{
id: "children",
onClick: () => console.log("Hello"),
},
"Hello World"
);
Way better than the regular JS declaration.
(As a side note, keep in mind that the last parameter is not mandatary, and you could give in the props with the children key)
React.createElement("div", { children: "Hello World" });
What if we have more than one child to render inside your div ?
const element = React.createElement("div", {
id: "children",
onClick: () => console.log("Hello"),
children: [
React.createElement("span", {
children: "Hello World, ",
style: { color: "red" },
}),
React.createElement("span", {
children: "this is ",
style: { color: "green" },
}),
React.createElement("span", { children: "Foo Bar !" }),
],
});
The children property accept an array of elements, and obviously you could do that as long as you want, and this is actually how your JSX code look like in reality.
If you have been using React for a bit before reading this, you should now have a better insight of why you're doing certain things (such as style={{color: 'red'}})
, but we'll come on it later.
Well, I ain't writing that anyway, how does this is useful ?
Indeed, this is pretty annoying to write, and nobody using React will use it with the raw API. That's where React introduced JSX.
JSX is basically a sugar synthax for writing the code above, thanks to Babel. (If you don't know what Babel is, it bascially take your code, and convert it into a browser compatibile version, more infos here).
So if you write that :
const component = () => <div id="children">Hello World !</div>;
It'll actually be compiled by Babel as :
const component = React.createElement("div", { id: "children" }, "Hello world");
Now, what if we rewrite the previous example with the list of elements in JSX ? It would look like this :
const component = () => (
<div id="children">
<span style={{ color: "red" }}>Hello World, </span>
<span style={{ color: "green" }}>this is </span>
<span>Foo Bar !</span>
</div>
);
Amazing, isnt it ? It's way cleaner than the raw React API.
Let's recap what we learned so far, but starting from JSX :
You write your JSX code, which is compiled by Babel to make actually readable.
The result is a call to the React.createElement() function with the correct parameters.
And now what ? Well, React is doing one more trick for us : He's making one more abstraction, and doing the document.createElement() function for us.
As an exemple, I've been digging and I found a pseudo code wrote by Dan Abramov.
var node = document.createElement(type);
Object.keys(props).forEach((propName) => {
if (propName !== "children") {
node.setAttribute(propName, props[propName]);
}
});
children.filter(Boolean).forEach((childElement) => {
var childNode = mount(childElement);
node.appendChild(childNode);
});
We see that React is doing exactly what we did at the beginning, create a new node, setting attributes if needed and appending it into the DOM with the help of the virtual DOM (I'll probably talk about it in another blog post).
You can also find the full pseudo code here.
Miscellaneous
Why I am passing an object for style inside the JSX ?
Whenever you'll want to apply inline style to your JSX element, you'll have to wrap the styles inside an object. Why ? Because doing the following won't make any sense :
const element = React.createElement(
'div',
{
id: 'children',
onClick: () => console.log('Hello'),
// Your browser would obviously complain
style : color : red
},
'Hello World');
Right ? And this is exactly what you're telling Babel to do by writing this :
<div style={color: 'red'} >Hello World</div>
And that's also why you can't embed any kind of statements inside your JSX, such as if...else.
How Babel does understand the difference between an html tag, and a custom component ?
By capitalizing your component. That's all. If you create a component without capitalizing it, Babel will understand it as a potention html tag, and so will create it.
<component>My custom component</component>
Not what we wan't.
Why do we need to wrap our elements into one parent ?
It's because on how the React API work, let's say you write this :
const component = (
<div>Hello </div>
<div>World !</div>
);
React will complain about having a parent, because Babel will compile it this way :
const element = React.createElement('div',
{
id: 'children',
children: 'Hello World !', 'ae'
}
);
Weird again, right ? You could wrap everything into an array, and return this way :
const component = [
<div>Hello </div>
<div>World !</div>
];
But this is not really how it is suppose to be used, hence why you need a parent.
Ending
We'll wrap it up for now, hope your enjoyend and learnt something. Again, feel free to send my feedback nor mistakes, I'll appreciate it !
You can find the original article on the Othrys website and you can follow my Twitter or tag me here to discuss about this article.
Have a nice day.
Top comments (0)