React is one of the most popular tools out there… but sometimes, it can feel like a maze, right? Where do you even start?
Well, let’s piece this puzzle together and explore the best ways to make the most of it. We will understand what are its main features, and what are the best ways to use it. Let’s dive into how to use Vite with React, work with components, and explore some other key concepts in React to help us get started on building web apps.
Table of Contents
- First React concepts and Vite
- JSX = JS + HTML
- What React is: componentization
- React: Next Puzzle Pieces
- References
First React Concepts and Vite
First, let’s see its core definition. The official documentation tells us the following:
React lets you build user interfaces out of individual pieces called components. Create your own React components like Thumbnail, LikeButton, and Video. Then combine them into entire screens, pages, and apps.
Okay, so we get it—React is a big deal in web development. But before jumping into setting up a React app, let’s clear up some concepts that often trip people up.
Since React helps us build web pages, it’s crucial to understand this mysterious DOM that everyone keeps talking about. But what exactly is it? Well, according to the MDN Web Docs:
The Document Object Model (DOM) is a programming interface for web documents. It represents the page so that programs can change the document structure, style, and content. The DOM represents the document as nodes and objects; that way, programming languages can interact with the page.
In short, the DOM is essential for creating dynamic web applications using JavaScript. It serves as a Web API for interacting with the structure and content of web pages. Although the DOM is not a programming language itself, it teams up with JavaScript to change elements on the page.
And you’ve probably heard of ReactDOM, right? But it’s important to understand that React is not the same as ReactDOM. Although they are part of the same ecosystem, they serve different purposes.
ReactDOM is the bridge between React’s Virtual DOM and the browser’s actual DOM. It provides the functions to render components we create in React onto the real DOM in the browser.
React, as I just mentioned, uses the Virtual DOM. It is an abstraction of the browser’s DOM, bacially a virtual representation of it. It allows React to update the actual DOM in a more performant way.
With that being said, it’s also important to know that React programming is declarative, not imperative. This means that, with React, we don’t have to explicitly define how every UI update happens step by step. Instead, we simply describe what we want the UI to look like based on the current state, and React takes care of updating the DOM with the help of ReactDOM. We’ll see declarative components in practice in the next topics.
Lastly, it’s important to note that React is a library (not a framework!). While React handles the core logic for building user interfaces, it relies on additional tools to create more complete applications.
In this blog post, we’ll explore how to use one of these tools, Vite:
Vite (French word for “quick”, pronounced
/vit/
, like “veet”) is a build tool that aims to provide a faster and leaner development experience for modern web projects.
This tool supports TypeScript, JSX, CSS, and more. Also, Vite is opinionated, and comes with some standards right from the start. React, on the other hand, is unopinionated and does not dictate how the creation of certain features should occur.
However, if you explored creating a React application some time ago, you might have encountered the build setup Create React App (CRA) officially supported by React. But we won’t be using it for several reasons related to the great advantages of Vite:
Vite doesn’t build the entire application before serving it. Modules are only processed when needed. In contrast, CRA doesn’t work this way, which can affect the performance of the development server. ⏳ (If you want to dive deeper into the advantages I’m mentioning, check out the documentation here).
Vite offers a shorter wait time for file updates thanks to Hot Module Replacement (HMR) using native ESM. This means that HMR updates are super fast, no matter the size of your app! ⚡
Even if you’re building a small app for study purposes and don’t see the performance difference right away, it’s good practice to get used to the tool that provides more speed for larger, more complex applications. 🚀
CRA is tied to React, while Vite is a build tool that’s agnostic to libraries and frameworks. 🌱 It’s gaining popularity fast and is a great choice if you plan to explore other frameworks like Vue.js.
Great! So we’ve seen how flexible and fast Vite is. And, if you also want to check out some Vite templates created by the community, you can find several here that can be used for Vue, Electron, some plugins, and other options.
Knowing all this, let’s start a project with Vite.
First, create a folder called “project”. Then, right-click on the folder and open the terminal from there. To start your project, make sure you have Node.js installed. If you haven’t installed it yet, check out this guide on how to install it with asdf for your machine.
After this setup, you have two options:
npm create vite@latest .
or
npm create vite@latest . -- --template react
The .
(dot) means the project will be created in the current folder. Also, the main difference between the two methods is that the second option uses a Vite template, so you won’t have to set up anything else. After running it, you’ll get a React project with just one command.
We’ll use the first command to explore Vite’s options, but feel free to pick either method; they both work the same way. So, when you type npm create vite@latest .
, you’ll see the options:
We’ll be using React, of course. But you saw that Vite is super handy even for apps that don’t use React—it works with any framework.
Next up, we need to pick a language. To simplify our implementation, we’ll stick with JavaScript for this example. Once you’ve made the choice, open it in your favorite IDE. You’ll notice some new folders in your project, but don’t worry—it’s not too complicated:
- The
src
folder is one of the most important in your project, and you will constantly be adding other folders and files to it. This is where our main component and our app reside, along with their styling files. - The
public
folder typically contains icons and media files. We add static files to this folder. - The
vite.config.js
file currently contains our React plugin. However, other plugins and build configurations, as well as CSS and import aliases, can also be configured there. For now, we won’t modify it, as the current plugin is sufficient for our needs.
Now, let’s follow the instructions in the terminal and type npm install
, followed by npm run dev
to see our initial app.
Open the app with the specified URL, and voilà:
Pretty cool, right? This screen pops up thanks to the App.jsx
entrypoint file, where we return a component with the same name.
But wait, why is the file extension .jsx
instead of .js
like we usually see out there? What’s the difference, and why does it matter? Let’s find out.
JSX = JS + HTML
It’s important to mention that JSX and React are not the same thing. Let’s understand why.
In fact, the documentation itself provides the answer:
JSX and React are two separate things. They’re often used together, but you can use them independently of each other. JSX is a syntax extension, while React is a JavaScript library.
As you noted from the subtitle above, JSX is close to HTML. We need it to combine the logic from JavaScript with the HTML structure required for a web page.
The documentation also mentions that JSX is commonly used:
Although there are other ways to write components, most React developers prefer the conciseness of JSX, and most codebases use it.
In the past, making web pages meant keeping things pretty separate: logic was in JavaScript, styling was in CSS, and structure was in HTML. But as the internet blew up in popularity, websites needed to get more interactive and responsive.
Now, a lot of a page’s content changes based on how users act. Instead of being static, content became dynamic. This means logic and HTML structure started mixing a lot more.
Let’s check out this difference with a simple example of a list. Below, we have a list of cats made just with HTML:
<h1>Iasmim's list of Cats</h1>
<img
src="https://example.com/fake-cat-image.jpg"
alt="Cool cat image description."
class="photo"
>
<ul>
<li>Maine Coon
<li>Sphynx
<li>Siamese
</ul>
How can we turn the list above into a React component using JSX? Just copying and pasting that code into the return of a component won’t work—there are some rules to follow for JSX to work right:
Only a single item should be returned; however, this item can contain other items within it. In our case, we have three items:
h1
,img
, andul
. We cannot return these three without a wrapper. We need a parent tag. This is because, even though JSX looks like HTML, it is JavaScript under the hood. This means that a function cannot return two objects unless they are wrapped in an array. In the case of the cats list, we can use an HTML tag such asdiv
or a fragment.Tags must be explicitly closed. JSX requires this, and in our cats list, the
li
andimg
tags do not follow this rule.camelCase(a convention for writing phrases without spaces) should be used, except in specific cases. Remember that JavaScript has limitations regarding variable naming? This means that many attributes that would typically be written as
stroke-width
are written asstrokeWidth
. Additionally, reserved words cannot be used, such asclass
. In our case,class
will becomeclassName
.
Okay, let’s check how our list looks after the changes:
import React from "react";
export const CatList = () => {
return (
<>
<h1>Iasmim´s list of Cats</h1>
<img
src="https://example.com/fake-cat-image.jpg"
alt="Cool cat image description."
className="photo"
/>
<ul>
<li>Maine Coon</li>
<li>Sphynx</li>
<li>Siamese</li>
</ul>
</>
);
};
Notice that we used a fragment as the wrapper for our component. According to the documentation:
Fragment
, often used via...</>
syntax, lets you group elements without a wrapper node.
We can use a fragment when we don’t want to wrap things in an extra div
, maybe for styling reasons. Fragments don’t change anything in the DOM; it’s like the elements aren’t grouped at all. Just remember, you can’t pass props to it.
Now we see why our App.jsx
needs to be written the way it is and why JSX looks a bit different from standard HTML.
With that in mind, let’s dive into how we create apps with components in React!
What React is: Componentization
Now that we know what React isn’t and have set everything up, let’s dive into how it can change our approach to building apps. We’ll see just how clear and straightforward React’s declarative components can be.
And a vital part of understanding the mindset behind React is componentization itself. According to the documentation:
React can change how you think about the designs you look at and the apps you build. When you build a user interface with React, you will first break it apart into pieces called components.
The common step-by-step process when developing apps with React would be:
- Organize the main goal of your page/application into small parts.
- Define the different states of your components.
- Connect these components according to the flow you have in mind for the application.
Let’s follow this step-by-step process.
We’ll create a simple app that submits a form with some data. It will also show a preview as the fields are filled in, so users can see what they’re submitting.
Meaning we can define the main objective: to allow the filling out of a form and to display its preview.
Notice that we can already define at least two components: Form
and Preview
. We can add these two components to our App.jsx
:
function App() {
return (
<>
<Form />
<Preview />
</>
);
}
export default App;
Currently, they are just simple components with headings. I placed them in a new folder called components
. Their paths are src/components/Form.jsx
and src/components/Preview.jsx
:
Preview:
import React from "react";
export const Preview = () => {
return (
<div>
<h2>This is the preview!</h2>
</div>
);
};
Form:
import React from "react";
export const Form = () => {
return (
<form>
<h1>This is a form!</h1>
</form>
);
};
Notice how we only declare what we want to appear in our application. This means we are using declarative programming. Imperative programming deals with how everything is done and requires a manual list of steps, for example. However, with React, we don’t need to worry about how these components should appear or how they are added to the DOM. We simply tell React what we want to be displayed. In other words, React is declarative and not imperative.
Note: If you’re using VSCode, check out the # ES7+ React/Redux/React-Native snippets extension. It lets you quickly create component bodies by typing “rafce” alongside other handy snippets! The component will match the file name where you use it.
Now, it is important to identify the states that can be shared between these components. I want the user to fill out the data in the form, and the preview should be able to display it as soon as it is filled in. Therefore, we understand that these two components will share the state: form data.
To achieve this, we will use Hooks. According to the documentation:
Hooks let you use different React features from your components. You can either use the built-in Hooks or combine them to build your own.
They help us update and share states, as well as cause effects in our components. There are several built-in hooks in React, but we can also create our own Hooks, which are referred to as custom Hooks.
Both Hooks and custom Hooks must follow certain rules:
- Only call Hooks at the top level of a function component or custom Hook.
- Only call Hooks from React functions; they do not work in regular JavaScript functions.
- They must have the prefix “use”. Examples include
useState
,useEffect
, anduseFormData
. (Your code will still work if you create a custom Hook that doesn’t start with this prefix, but it is a naming convention that should be followed if you want to maintain good practices.)
There are a few other rules to keep in mind, but these should be enough to get us started with some of React’s built-in Hooks. Let’s check out a few:
useState: This allows you to update a state that can be a string, array, object, etc. For example, you can use it to update the user’s name. It provides you with the value (the most recent value contained in useState) and the setValue (the function that updates the value).
const [firstName, setFirstName] = useState("");
useContext: This allows us to share states between distant components. For example, sharing the dark/light theme toggle across the entire application.
function ToggleThemeButton() { const theme = useContext(ThemeContext); // ... rest of the component }
useEffect: This is used to synchronize the component with some external system. For instance, connecting to a chat server.
useEffect(() => { const connection = createConnection(serverUrl, roomId); connection.connect(); return () => { connection.disconnect(); }; }, [roomId, serverUrl]);
When building apps with React, you’ll run into situations that need various Hooks. There are even more for specific cases—check them out here.
In our app, we’ll use Hooks inside custom Hooks to get things done.
We’ll use useState
to manage the form data, including the user’s name, city description and their very important preferences, like whether soup counts as dinner. We’ll also have a function called setFormData
to update the state and reset it after submission.
function App() {
const [formData, setFormData] = useState({
firstName: "",
lastName: "",
cityDescription: "",
favoriteSeason: "",
soupIsDinner: "",
areTermsAccepted: false,
});
const handleFormSubmit = (event) => {
event.preventDefault();
alert("Information submitted! :D");
setFormData({
firstName: "",
lastName: "",
cityDescription: "",
favoriteSeason: "",
soupIsDinner: "",
areTermsAccepted: false,
});
};
return (
<>
<Form />
<Preview />
</>
);
}
Cool, right? But we still need to make sure our components get these values. How do we do that?
We can pass this info to our components using props. Our Preview
and Form
components need some properties to work well together, don’t you think? For example, formData
is one of those properties.
According to the documentation:
React components use props to communicate with each other. Every parent component can pass some information to its child components by giving them props. Props might remind you of HTML attributes, but you can pass any JavaScript value through them, including objects, arrays, and functions.
And to give our components access to these properties, we can pass them like this:
return (
<>
<Form
formData={formData}
setFormData={setFormData}
onSubmit={handleFormSubmit}
/>
<Preview formData={formData} />
</>
);
Our components can receive them like this:
Form:
export const Form = ({ formData, setFormData, onSubmit }) => {
const { firstName, lastName, cityDescription, favoriteSeason, soupIsDinner, areTermsAccepted } = formData;
return (
// ... rest of the component
);
};
Preview:
export const Preview = ({ formData }) => {
const { firstName, lastName, cityDescription, favoriteSeason, soupIsDinner, areTermsAccepted } = formData;
return (
// ... rest of the component
);
};
In the components, I used object destructuring to grab the values we need. This way, we can just use firstName
instead of calling formData.firstName
every time.
Note: Props and state are different! Think of props as arguments for your components, while state is like the component’s memory. They work together—like in our case, where we pass state using props.
Now, in the new form, we’ll have radio buttons, inputs, checkboxes, and a text area. As we’ve seen, it’s crucial to break down features into components in React. So, let’s create smaller components to build the parent component, Form
.
Let’s start with the basics: the input. Below is the Input
component I created:
import React from 'react';
import './Input.css';
export const Input = ({ label, value, onChange }) => (
<div className="input">
<label className="input__label">{label}</label>
<input
className="input__field"
type="text"
value={value}
onChange={onChange}
required
/>
</div>
);
Wait, there are some new things in this component, such as the class names and the use of label
with curly braces. What’s that about?
No worries, I’ll explain! The curly braces let us use variables, props, or state in JSX. In the example above, I’m just taking the value of label
and using it for my input’s label. The documentation backs this up:
Sometimes you will want to add a little JavaScript logic or reference a dynamic property inside that markup. In this situation, you can use curly braces in your JSX to open a window to JavaScript.
I’m also importing a new file called Input.css
, which is in the same folder as our new components—the components folder. This folder will have several .jsx
and .css
files as we keep building more components.
The .css
file is one way to style a component. Importing the CSS lets us use class names to change the styles, and it’ll only affect the component that imports it.
You probably also noticed that I used className
for the styling classes in the component above. This is due to JSX, which we talked about earlier. But the new thing is that I’m also using BEM to name the HTML elements for styling. It helps keep our app organized and makes it easier to understand the styling hierarchy.
Now, just like we did with the Input
component, we can create all the other components we need for our Form. After that, we can import them into the parent component:
import React from "react";
import { Input } from "./Input";
import { TextArea } from "./TextArea";
import { Select } from "./Select";
import { RadioGroup } from "./RadioGroup";
import { Checkbox } from "./Checkbox";
import "./Form.css";
export const Form = ({ formData, setFormData, onSubmit }) => {
const { firstName, lastName, cityDescription, favoriteSeason, soupIsDinner, areTermsAccepted } = formData;
return (
<form onSubmit={onSubmit}>
<Input
label="First name"
value={firstName}
onChange={(e) =>
setFormData({ ...formData, firstName: e.target.value })
}
/>
<Input
label="Last name"
value={lastName}
onChange={(e) => setFormData({ ...formData, lastName: e.target.value })}
/>
<TextArea
label="City description"
value={cityDescription}
onChange={(e) =>
setFormData({ ...formData, cityDescription: e.target.value })
}
/>
<Select
label="Favorite season"
value={favoriteSeason}
onChange={(e) =>
setFormData({ ...formData, favoriteSeason: e.target.value })
}
/>
{/* ... rest of the component */}
</form>
);
};
Great! We’ve got better componentization now. But I mentioned that we would also use custom Hooks, right? One way to make our components more concise and achieve better separation of concerns would be to use custom Hooks.
Back to App.jsx
, we can enhance our use of Hooks by creating a custom Hook. It’s pretty much a customized Hook that utilizes other React Hooks. Notice that throughout our application, we will constantly use formData
. It is essential for many components. How about we transform this necessity into a custom Hook?
Let’s create it in the src/hooks
directory. We’ll make a file called useFormData.js
, where we can set up the necessary state and functions for the app. Basically, we’ll take everything from App.jsx
related to formData
and move it into this custom Hook. That makes our app component more organized and smaller.
Below we can see the custom Hook code:
import { useState } from "react";
export const useFormData = () => {
const [formData, setFormData] = useState({
firstName: "",
lastName: "",
cityDescription: "",
favoriteSeason: "",
soupIsDinner: "",
areTermsAccepted: false,
});
const resetFormData = () => {
setFormData({
firstName: "",
lastName: "",
cityDescription: "",
favoriteSeason: "",
soupIsDinner: "",
areTermsAccepted: false,
});
};
const handleFormSubmit = (event) => {
event.preventDefault();
alert("Information submitted! :D");
resetFormData();
};
const isAnyFieldFilled = () => {
const {
firstName,
lastName,
cityDescription,
favoriteSeason,
soupIsDinner,
areTermsAccepted,
} = formData;
return (
firstName ||
lastName ||
cityDescription ||
favoriteSeason ||
soupIsDinner ||
areTermsAccepted
);
};
return {
formData,
setFormData,
handleFormSubmit,
isAnyFieldFilled,
};
};
Our app component ends up like this:
import React from "react";
import { Form } from "./components/Form";
import { Preview } from "./components/Preview";
import useFormData from "./hooks/useFormData";
import "./styles/App.css";
function App() {
const { formData, setFormData, handleFormSubmit, isAnyFieldFilled } =
useFormData();
return (
<div className="app">
<div className="app__container">
<div className="app__form">
<h1 className="app__title">Form</h1>
<Form
formData={formData}
setFormData={setFormData}
onSubmit={handleFormSubmit}
/>
</div>
<Preview formData={formData} isAnyFieldFilled={isAnyFieldFilled} />
</div>
</div>
);
}
export default App;
Pretty nice and more focused on declaring only how the component should appear, right? Also, above you can see that I pass the isAnyFieldFilled
function to my Preview
component. My component looks like this now:
export const Preview = ({ formData, isAnyFieldFilled }) => {
// component implementation
};
And that allows me to utilize conditional rendering in our React application. This is a special syntax that allows us to write conditions in JSX. In our case, we will use it to ensure that the form items in the Preview
only appear when they are filled out:
export const Preview = ({ formData, isAnyFieldFilled }) => {
const {
firstName,
lastName,
cityDescription,
favoriteSeason,
soupIsDinner,
areTermsAccepted,
} = formData;
const favoriteSeasonColor =
SEASONS.find((season) => season.value === favoriteSeason.toLowerCase())
?.color || "gray";
return (
<div className="preview" style={{ borderColor: favoriteSeasonColor }}>
<h2 className="preview__header">Information Preview</h2>
{isAnyFieldFilled() ? (
<>
{(firstName || lastName) && (
<p className="preview__item">
<span className="preview__label">Name:</span> {firstName}{" "}
{lastName}
</p>
)}
{cityDescription && (
<p className="preview__item">
<span className="preview__label">City description:</span>{" "}
{cityDescription}
</p>
)}
{/* ... rest of the component */}
);
};
In the code snippet above, we check if the isAnyFieldFilled
function returns true or false. Based on that, a block of JSX will be rendered. The name also reflects whether firstName
or lastName
exists. If either is true, the &&
operator lets us render what’s in parentheses. The same applies to the other components.
Also, you might’ve noticed that the Preview
component’s border changes color based on the selected season. I also made it show the least favorite seasons using an array from a constants.js
file in a helpers folder. This way, the information is more customized and complete for the user. Here’s the result:
In the end, we used Hooks, conditional rendering, JSX, and props to create a very reactive React application where many of its parts are modified by user actions.
Sticking to the same principles we’ve talked about, I added a feature that shows different warning messages in the TextArea. Check it out below, with a character limit of 50:
So I invite you to apply what you’ve seen here by trying to add that warning message feature as well. Remember that there are several different ways to achieve this, whether through custom Hooks, conditional rendering, or even JavaScript helper functions.
After trying, you can check my solution here. You can also check out our small application final version here. 🙂
React: Next Puzzle Pieces
In this blog post, we dove into how componentization works in practice. We even touched on custom Hooks and conditional rendering. Our app is small, but we broke it down into components in the React way!
But hey, there are still more best practices to explore in React! Check out this article for awesome tips to level up your code.
So, keep pushing your skills. Make your app more complex, add fetch calls, or try sharing context with useContext. Happy coding! 😀
Previously: Linters, Formatters… Same Thing? — Static Analysis Clarified
This post is part of our ‘The Miners’ Guide to Code Crafting’ series, designed to help aspiring developers learn and grow. Stay tuned for more and continue your coding journey with us!! Check out the full summary here!
References
- https://clouddevs.com/react/and-reactdom/
- https://ui.dev/c/react/imperative-vs-declarative
- https://semaphoreci.com/blog/vite
- https://developer.mozilla.org/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_components
- https://medium.com/@miahossain8888/how-to-create-a-react-app-with-vite-571883b100ef
- https://vite.dev/guide/
- https://react.dev/learn/writing-markup-with-jsx
We want to work with you. Check out our "What We Do" section!