(This tutorial is written using React v17 however the general concepts will apply to any version)
Table of Contents
- What is React
- Prerequisites
- Initializing the Project
- Installing React
- Creating our First Component
- Functional Components and Class Components
- Component State
- Reusing Components
- JSX
- Wrapping Up
What is React?
React is a Javascript library that gives you tools to group HTML elements together with the different possible states of those elements into single entities called components that are both customizable and reusable.
That may be a bit difficult to visualize, so to illustrate, imagine a blog post such as this one you're reading now. Imagine every blog post needs a title, a hero image, content, and a little "heart" button at the bottom that can be clicked to like the post.
If you're familiar with HTML you can imagine the process of building it, and the challenge you would be faced with when tasked with updating the content on each new blog click, or managing the state of the heart button.
Although HTML does provide some of its own tools like the template element, React takes this basic concept to a whole new level.
Let's take a look at how a BlogPost
component might look in React:
<html>
<head>
<meta charset="UTF-8" />
</head>
<body>
<BlogPost
title="Learn React!"
content="Lorem ipsum"
heroImage="assets/cool_pic.png"
likeButton={<HeartButton />}
/>
</body>
</html>
Looking at that example, imagine how easy it would be to create a page with 10 different blog posts, each with its own unique title and content. See how the customization and reusability comes into play?
As exciting as that idea is, before we get to that point there are a number of basic concepts we need to understand first.
Keep in mind the goal of this blog series is focused on the first word: Understanding. We are trying to dissect and understand all the different pieces that comprise the modern web stack.
The goal here isn't to teach you everything about React's features and syntax (the official documentation is the best source for that). The goal is to help you build a stronger mental model of what it is, why it's used and how to implement it into your own projects.
Having that foundation will make it significantly easier to learn those features and become productive with React sooner. So with that said, let's move onto the building blocks.
Prerequisites
You will need to have Node.js installed on your machine and available from your terminal. Installing Node will automatically install npm as well, which is what you will use to install Babel.
If you see version numbers when running the two commands below (your numbers will likely be different than this example) then you are ready to go:
node --version
> v15.5.0
npm --version
> 7.16.0
You will need a solid understanding of Javascript.
Not only do you need to have a good grasp on the fundamentals (strings, numbers, arrays, objects, functions), but there are a number of other Javascript patterns that appear frequently in React codebases. Here is a non-exhaustive list of some of the ones that come up frequently:
- Destructuring assignment
- Arrow functions
- Conditional (ternary) operator
- Class syntax
- Template strings
You will want to ensure you understand what each of these are before you begin your React journey, that way you can focus all your attention on React patterns rather than on Javascript itself.
If you are unfamiliar with any of the above then it would be worth your time to work your way through the fundamentals sections of javascript.info.
You will also want to have a good understanding of what the DOM is. In particular, DOM methods like document.querySelector().
Although one of the main purposes of React is to provide an environment where we don't need these methods at all, knowing them will give you a significant leg-up in understanding why React was created and what problems it solves.
Initializing the Project
Let's start by initializing a new npm
project. Run the following command to generate one:
npm init -y
The -y
flag will automatically select default values for everything, which is appropriate in our example.
Installing React
Next we will add React to our project:
npm install react react-dom
Let's take a look at what each one is doing:
react
serves as the core engine that manages all the components and their states.react-dom
is what allows React to do its work on our DOM elements in our web application. The reason they are separate is that gives React the flexibility to work on other environments beyond just the web browser. Another environment besides the DOM where React can operate is on mobile devices through React Native for example.
So to begin, we will create our first React element.
Let's start by loading the React package from node_modules
. Presuming that index.html
and node_modules
are in the same directory:
index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<script src="node_modules/react/umd/react.development.js"></script>
<script src="node_modules/react-dom/umd/react-dom.development.js"></script>
<script src="script.js" defer></script>
</head>
<body>
<div id="app"></div>
</body>
</html>
It's important that our <script>
tag for our own script.js
file has the defer
attribute. The reason being that we need the <div>
with the id="app"
to exist before our React code runs so that it has somewhere to mount to.
Creating our First Component
Next let's create our script.js
file with our first component. Our first component will be a simple button, and along the same lines as the official React documentation, it will be "like" button:
script.js
const LikeButton = () => {
return React.createElement(
"button",
{ className: "like-button" },
"Click to like!"
);
};
const domContainer = document.querySelector("#app");
ReactDOM.render(React.createElement(LikeButton), domContainer);
React elements are reacted with the React.createElement
function. It takes three parameters:
The type of element. If this is a string it will create a DOM node of that type, a
button
in our example will create a<button>
element. This can also be another React component instead of a string.The props of the component. These are similar to HTML attributes and in fact will extend the attributes if you are using them directly on an HTML element like our
button
here. You can also define your own custom props to make your components more reusable. If your component has no props this argument can benull
. Some attributes in React are slightly different from their HTML counterparts: for exampleclassName
instead ofclass
, sinceclass
is already reserved in JS to refer to a class.The
children
of an element, which is how you create the nesting behavior of DOM nodes. The children can be a string, or more React elements. Components can have as many children as they want. In our case the child is simply text.
The React.render()
function takes our LikeButton
component and mounts it on any DOM node that we pass to it.
Once the initial component is mounted everything from that point on can be created inside of that root node. The document.querySelector("#app")
call that we make to get the mounting node should the the only manual call to querySelector in our entire React application.
Based on our understanding of these functions, we would expect this to create a DOM structure that looks like:
<div id="app">
<button class="like-button">Click to like!</button>
</div>
Give it a try now and serve up your index.html
and take a look at the output. You should have a button on your page with a DOM structure as shown in the below screenshot:
Functional Components and Class Components
Although this tutorial focuses on the more modern method of creating components as functions, it is important to make sure you are also familiar with the older style of creating components as classes.
Class components are still fully supported in React and you're still very likely to encounter them in real codebases and projects, so understanding how they work is important. For most users, particularly those just learning React, there is no difference. For more advance users there are a small subset of scenarios where class components are still required (e.g. error boundaries).
Most new features in React are designed around functional components (e.g. hooks) so for new projects and people learning the library, functional components are recommended. In practice the best choice is to follow whatever convention your team has established.
If you're curious, here's how our LikeButton
component would look using class
syntax. You do not need to update your code as this is only for demonstration. A little bit more verbose, but accomplishes the same behavior:
class LikeButton extends React.Component {
constructor(props) {
super(props);
this.state = { liked: false };
}
render() {
if (this.state.liked) {
return React.createElement("span", null, "Liked!");
}
return React.createElement(
"button",
{
className: "like-button",
onClick: () => this.setState({ liked: true }),
},
"Click to like!"
);
}
}
const domContainer = document.querySelector("#app");
ReactDOM.render(React.createElement(LikeButton), domContainer);
(Note that this is only for demonstration, our tutorial will continue with the existing functional component syntax)
Component State
You may have noticed that despite saying "click to like", our button doesn't actually have any click functionality. Let's say that when the button is clicked, we would like it to disappear and be replaced with an <span>
tag that says "Liked!".
If were were using plain old Javascript that would require us to use a lot of manual DOM methods, including querySelector
to select all the nodes we are working with, as well as createElement
to create our new <span>
and appendChild
to add it as a child to our #app
node.
Let's see how to accomplish the same thing using React, without the need for any DOM methods. We'll do this in a two-step process to help understand the concept of state in a component.
Replace the content of script.js
with the new code below:
script.js
const LikeButton = () => {
let liked = false;
if (liked) {
return React.createElement("span", null, "Liked!");
}
return React.createElement(
"button",
{
className: "like-button",
onClick: () => {
liked = true;
console.log("button was clicked");
},
},
"Click to like!"
);
};
const domContainer = document.querySelector("#app");
ReactDOM.render(React.createElement(LikeButton), domContainer);
(Note that in React, the normal lowercase onclick
attribute you use in HTML files becomes the more Javascript idiomatic onClick
. Make sure to be aware of this subtile difference. To help avoid this common syntax error you can use a linter
, which will be discussed further in upcoming tutorials).
You can see that the initial state of our LikeButton
component is that liked
is false
. We're not going to render the span
because that only occurs when liked
is true
. We render a button
with an onClick
event handler that will set the value of liked
to true
.
You can confirm the onClick
handler is running by viewing the dev console and seeing out "button was clicked" message. Unfortunately despite being logically sound, the state of the button doesn't change.
Although we confirm that we are changing the value of liked
, our issue is that there is nothing specifically telling React "hey, our component has changed, can you please render it again and update the DOM for us with the new state?"
What we have to do is introduce a method to inform React about the changing state of our component. We can do that with the setState hook.
Our updated LikeButton
now looks like this:
script.js
const LikeButton = () => {
const [liked, setLiked] = React.useState(false); // <-- NEW
if (liked) {
return React.createElement("span", null, "Liked!");
}
return React.createElement(
"button",
{
className: "like-button",
onClick: () => {
setLiked(true); // <-- NEW
console.log("button was clicked");
},
},
"Click to like!"
);
};
const domContainer = document.querySelector("#app");
ReactDOM.render(React.createElement(LikeButton), domContainer);
You can see two small changes highlighted with the "NEW" comments.
The first line in our updated LikeButton
function component uses Javascript's array destructuring syntax. Make sure you have a good familiarity with that syntax so that you don't confuse it with the useState
function itself.
React's useState
function returns an array with two values:
The first is a variable with the same value that was passed (in our case
liked
which will befalse
).The second array value is a
function
that is used to change the value ofliked
in a way that React will respond to and re-render the component (update the DOM with the new state).
Stateful variables keep their value even when the component re-renders. They will not be reset to defaults. This is what causes the component to now take the conditional if
branch and render the span
element instead of the button
.
Try it yourself!
Reusing Components
We've now created our first React component that manages its own state without the use of DOM methods. It might be a bit difficult to see the real advantage of this at such a small scale. Let's try and imagine how this might come in handy at a larger scale.
Imagine you have a Facebook-like interface with 5 posts, each with their own like button. If using traditional DOM methods, you would need to use make sure you could target the specific button that was clicked and update it. This might get fairly complicated depending on what kind of selector you are using.
With our button that handles its own state, it's as easy. We create a wrapper <div>
with a few styles to display the buttons as a vertical column. Within that wrapper are five separate LikeButtons
each with their own independent state:
script.js
// ...
const domContainer = document.querySelector("#app");
const manyButtons = React.createElement(
"div",
{ style: { width: "100px", display: "flex", flexDirection: "column" } },
React.createElement(LikeButton),
React.createElement(LikeButton),
React.createElement(LikeButton),
React.createElement(LikeButton),
React.createElement(LikeButton)
);
ReactDOM.render(manyButtons, domContainer);
This same basic paradigm can be extended as far as you can imagine to create larger and more complex components, each with or without their own state, and with children that also manage their own state. Combined together you can create a fully reactive application.
At this stage is when you start to see how the React.createElement
syntax can begin to feel pretty cumbersome. That's where JSX comes into play.
JSX
In this section we'll learn what JSX is and how to configure your environment to write your React components in JSX.
What is JSX?
JSX is simply a syntax extension of the Javascript language. It's not quite Javascript, but it's not HTML either. It's goal is to provide you with the tools to write your components in a way that describes how you want them to appear.
JSX Prerequisites
Browsers do not inherently understand JSX so we need some kind of translation tool. That tool is called Babel.
This tutorial will presume you have a solid understanding of how to setup Babel before you continue further. If you need to get up to speed, check out our previous tutorial first:
Understanding the Modern Web Stack: Babel
Installing Babel
Run the following command from the root directory of your project:
npm install @babel/core @babel/cli @babel/preset-env @babel/preset-react --save-dev
The first three dependencies were described in the previous tutorial, the new one is:
-
@babel/preset-react
- This preset understands how to parse JSX and transform it into Javascript code that the browser can process.
Next we need to add instructions for Babel so it knows to use the preset-react
when its run. Update your package.json
file with the following:
package.json
{
"dependencies": {
"react": "^17.0.2",
"react-dom": "^17.0.2"
},
"devDependencies": {
"@babel/cli": "^7.15.7",
"@babel/core": "^7.15.5",
"@babel/preset-env": "^7.15.6",
"@babel/preset-react": "^7.14.5"
},
"browserslist": ["last 2 Chrome versions"],
"babel": {
"presets": [["@babel/preset-env"], ["@babel/preset-react"]]
}
}
Babel presets are run in reverse order, so Babel will first run the preset-react
to transform the JSX to Javascript, then it will run the preset-env
to transpile our code to accommodate our target browsers (in this case last 2 Chrome versions, so little if anything should change).
Converting to JSX
JSX files require a special extension to help the tooling understand when JSX is being used. If you are writing JSX in a Javascript file then simply change the extension from .js
to .jsx
.
Here's what our new script.jsx
file looks like updated to use JSX:
const LikeButton = () => {
let [liked, setLiked] = React.useState(false);
if (liked) {
// return React.createElement("span", null, "Liked! π");
return <span>Liked! π</span>;
}
return (
<button
className="like-button"
onClick={() => {
setLiked(true);
}}
>
Click to like!
</button>
);
};
const domContainer = document.querySelector("#app");
const ManyButtons = () => {
return (
<div style={{ width: "100px", display: "flex", flexDirection: "column" }}>
<LikeButton />
<LikeButton />
<LikeButton />
<LikeButton />
<LikeButton />
</div>
);
};
ReactDOM.render(<ManyButtons />, domContainer);
Note how much easier it is to reason about what the actual application is going to look like when it renders. In particular the ManyButtons
component makes it much clearer that you will be rendering a <div>
wrapper with five LikeButtons
inside of it.
We've also left in one comment above our <span>
to show a quick comparison between the standard React.createElement()
syntax and JSX.
Now let's transform it into raw Javascript so the browser can use it. Run the following command:
npx babel script.jsx --out-file script.js`
There should be no need to update your index.html
file since it originally pointed to script.js
, and that is the filename of our output. If we take a look at the file that Babel has created it looks like:
script.js
"use strict";
const LikeButton = () => {
let [liked, setLiked] = React.useState(false);
if (liked) {
// return React.createElement("span", null, "Liked! π");
return /*#__PURE__*/ React.createElement(
"span",
null,
"Liked! \uD83D\uDC4D"
);
}
return /*#__PURE__*/ React.createElement(
"button",
{
className: "like-button",
onClick: () => {
setLiked(true);
},
},
"Click to like!"
);
};
const domContainer = document.querySelector("#app");
const ManyButtons = () => {
return /*#__PURE__*/ React.createElement(
"div",
{
style: {
width: "100px",
display: "flex",
flexDirection: "column",
},
},
/*#__PURE__*/ React.createElement(LikeButton, null),
/*#__PURE__*/ React.createElement(LikeButton, null),
/*#__PURE__*/ React.createElement(LikeButton, null),
/*#__PURE__*/ React.createElement(LikeButton, null),
/*#__PURE__*/ React.createElement(LikeButton, null)
);
};
ReactDOM.render(
/*#__PURE__*/ React.createElement(ManyButtons, null),
domContainer
);
You'll notice it looks a lot like our original code before we used JSX. Now you can see how JSX allows us to write our components in a way that's easier for ourselves to reason about as human developers, and we can rely on our tools to convert them into raw Javascript when we're finished.
(If you're curious about the PURE comments they are annotations to help bundlers and minifiers remove unused code)
Bonus: Buttons Everywhere
A few small changes to our script.jsx
turns our buttons into a digital game of bubble wrap.
script.jsx
const LikeButton = () => {
let [liked, setLiked] = React.useState(false);
if (liked) {
// return React.createElement("span", null, "Liked! π");
// NEW
return <span style={{ width: "150px", height: "25px" }}>Liked! π</span>;
}
return (
<button
// NEW
style={{ width: "150px", height: "25px" }}
className="like-button"
onClick={() => {
setLiked(true);
}}
>
Click to like!
</button>
);
};
const domContainer = document.querySelector("#app");
const ManyButtons = () => {
return (
// NEW BLOCK
<div style={{ display: "flex", flexDirection: "row", flexWrap: "wrap" }}>
{[...new Array(500)].map((_, index) => (
<LikeButton key={index} />
))}
</div>
// END: NEW BLOCK
);
};
ReactDOM.render(<ManyButtons />, domContainer);
Make sure you remember to run it though Babel with:
npx babel script.jsx --out-file script.js
Note the three locations commented with "NEW" that have been changed. This examples demonstrates the power of Javascript's Array.map() combined with React to generate any arbitrary number of components dynamically.
(If you are still a little unsure about the syntax here, feel free to drop a comment and I'd be happy to explain in more detail! Don't worry if you didn't pick up this last part, it's just meant as a little bonus demo.)
If you have followed everything else up to this point then congratulations! You are in the perfect place to begin your React journey.
Wrapping Up
You should now have a solid grasp of the fundamentals of what React and JSX are, and how you can use them in your modern web stack to write more efficient and maintainable applications.
Please check out the other entries in this series! Feel free to leave a comment or question and share with others if you find any of them helpful:
The Modern Web Stack: Webpack - Loaders, Optimizations & Bundle Analysis
The Modern Web Stack: Webpack - DevServer, React & Typescript
@eagleson_alex on Twitter
Thanks for reading, and stay tuned!
Top comments (0)