Creating Reusable React Components: A Beginner's Guide
Learn how to build versatile and scalable components to streamline your React development process
React is one of the most popular JavaScript libraries for building complex web applications efficiently. One of the key benefits of React is its component-based architecture, which allows you to create reusable pieces of code that can be used across different parts of the application.
Creating reusable React components is not only a best practice, but it also improves your development process by reducing the amount of code you need to write, making your code more modular and therefore you can maintain your application more easily. However, if you are new to React, you might be wondering how to create reusable components effectively in your projects.
In this blog post, I'm going to provide you with a beginner-friendly guide to creating reusable React components. By the end of this blog post, you'll have a solid understanding of how to create scalable React components that can make your development process more efficient.
Prerequisites
This tutorial is completely beginner-friendly and suits anyone who has just started learning React. Here I list a few prerequisites which helps you to get the most out of this tutorial :
Familiarity with React - While not strictly required, having some familiarity with React will help you better understand the concepts we cover in this tutorial
Node.js - ReactJS is built on top of Node.js, which means you will need it installed on your machine. Node.js comes with npm (Node Package Manager), which we will use to install ReactJS and its dependencies.
Git - Git is a version control system used by developers to manage source code. While it's not strictly required to know Git for this tutorial, having a very basic understanding of Git might be helpful for you.
What Are We Going To Build?
In this tutorial, we're going to build a simple React app that uses three reusable components: a Container, a Button, and an Input. These components will allow us to create a UI that can be easily customized and reused in other projects.
The Container component is a flexible component that can be rendered as any HTML element (such as a
div
,section
, orarticle
). It also accepts custom CSS classes as a prop, which means we can easily style the component to fit our design needs.The Button component is a call-to-action component that can be customized with text, styles, and an onClick function. This allows us to create buttons that perform different actions depending on how we use them.
The Input component is a simple form input component that can be customized with various props, such as placeholder text, type (text, email, password, etc.), and value.
By using these three components, we can build a simple UI that allows users to enter information and perform actions with the click of a button. We'll walk through how to create each of these components step-by-step and show you how to put them all together to create a fully functioning app.
Here is how our application is going to look like at the end ๐๐
๐ Visit the Live demo
๐ Final Code
Getting Started
Well, Let's get started! If you want to follow along with me ( and I recommend you to do so ), I've already prepared a starter boilerplate project for you. You can follow these steps to get the starter files up and running on your machine :
Open up This Github Repository ( while you're there, be kind and leave a star for me ๐ ) and click on the green Code button on top. From here, you can click on Download ZIP. Once the file is downloaded on your machine, extract the files and proceed to the next step.
Open up the starter files in your favorite code editor, and run the following command :
npm install
Wait while npm installs the necessary dependencies, it might take some time. Once all the dependencies are installed, run the following command :
npm start
This command will run a local server on your machine. Now if you open up your favorite browser and navigate to http://localhost:3000/ you must see the following page :
Congratulations ๐! You've successfully got the app up and running, how cool is that? Here is a very quick walkthrough If you want to know more details about the boilerplate project :
A
components
directory, which contains all the reusable components for the project. In this directory, you'll find files for the Container, Button, and Input components we'll be building.An
index.css
file, which contains all the custom CSS we'll need for this project. We'll be using this file to style our components and make our app look great. I've already prepared all the CSS so you won't need to worry about styling and you can completely focus on React.An
App.jsx
file, which is the main component of our app. This file imports our reusable components from thecomponents
directory and uses them to build our UI.
Container Component: Basic Version
Let's build our first reusable React component. The Container component is going to wrap other React elements and it also can be easily modified by passing props. But before, I'm going to show you what a newbie React developer might do ( at least I used to do it this way ๐
)! Open up App.jsx
and add the following code to it :
// ./App.jsx
function App() {
return (
<div>
<div className="custom-container bg-secondary">
<p>this is container 1</p>
</div>
<div className="custom-container bg-secondary">
<p>this is container 2</p>
</div>
</div>
)
}
Save this file and open up your browser, you are going to see two containers, rendered successfully on the screen, Awesome! You are happy, React is happy, and Life is good! But is this really the best approach?
โ ๏ธ Look at the above code for a few seconds and try to find the problem!
Did you find it? Then you are a rockstar! if you couldn't notice the issue, don't worry! I'm here to explain it to you ๐:
The problem with this piece of code is repetition, we've repeated ourselves to get the same result. This approach is not optimized and while you might get away with it in a very small app, you'll definitely run into issues as your project scales up. What if you decide to add a default subtitle to every container in your app at the end stage of the development? Are you going to find each container across the whole application and add a default subtitle manually to them? I don't know about you, but I'm too lazy to do that ๐ . I'd rather change the container in one place and it gets applied across the whole application! Luckily we can do that with React ๐!
Now open up component/Container.jsx
and let's modify it :
// ./components/Container.jsx
export const Container = ({ children }) => {
return <div className="custom-container bg-secondary">{children}</div>
}
This is a very simple version of our Container component and it accepts a children
prop. In React, the children
prop is a special prop that can be passed to a component and it allows you to render whatever you include between the opening and closing tags of that component.
To better understand this concept, go back to App.jsx
and add the following code to it:
// App.jsx
import { Container } from './components'
function App() {
return (
<div>
<Container>
<p>this is container 1</p>
</Container>
<Container>
<p>this is container 2</p>
</Container>
</div>
)
}
Whatever goes between <Container></Container>
is passed as children
to the Container
, so now we can render whatever we want in the Container
.
From now on, whenever we change the Container
component, the change will be applied globally wherever we used this component! Don't believe me? Let's upgrade our Component
furthermore to make sure everything's gonna work just fine!
Container Component: Advanced Version
Congratulations on making it so far, Great job ๐ฅ! Now that we have a solid understanding of reusable components and children
prop, let's go beyond that and see what else we can do with React?
First of all, we want to be able to add our custom CSS classes to the container component. it's easy! just open components/Container.jsx
and edit it to the following code :
// ./components/Container.jsx
export const Container = ({ children, className = 'bg-secondary' }) => {
return (
<div className={`custom-container ${className}`}>
{children}
</div>
)
}
We created a className
prop and we gave it a default value of bg-secondary
, this CSS class will add a default gray background to the Container
, but once we pass a custom CSS class name to the Container
component, it will overwrite the default value. So let's try that!
Now if you open up your browser, you will see this ๐
Now open up App.jsx
and edit it :
// App.jsx
import { Container } from './components'
function App() {
return (
<div>
<Container>
<p>this is container 1</p>
</Container>
<Container className="bg-primary">
<p>this is container 2</p>
</Container>
</div>
)
}
We added a custom CSS class name to one of our Container
component and we left the other one with the default value, now let's check our browser to see what has happened ๐ค:
Wow! our default value has been overwritten and now the second container has a new look! Isn't that beautiful?
Although we've made awesome progress, I'm still not satisfied with this Container
component. I mean, now our Container
is always rendered as <div>
element, but what if we need an <ul>
as a container? or what if we need a <section>
as a container? what are we going to do then?
Let's fix that! Open up components/Container.jsx
and add the following code:
export const Container = ({
as: Element = 'div',
children,
className = 'bg-secondary',
}) => {
return (
<Element className={`custom-container ${className}`}>
{children}
</Element>
)
}
If this looks a little bit complex to you, Take a deep breath! don't worry, it's just fine!
In this code, Element
is a prop that is set to a default value of 'div'
. This means that if the Container
component is used without specifying an as
prop, it will render a <div>
element. However, if you want to use a different element instead of a <div>
, you can specify the as
prop like this:
<Container as="section">
This will be rendered inside a section.
</Container>
In this case, the Container
component will render a <section>
element instead of a <div>
element, because the value of the as
prop is 'section'.
Now let's try this in action and see what happens. Open up App.jsx
and add the following code:
// App.jsx
import { Container } from './components'
function App() {
return (
<div>
<Container>
<p>this is container 1</p>
</Container>
<Container className="bg-primary" as="article">
<p>this is an article</p>
</Container>
</div>
)
}
Although you're not going to see any visual difference, but if you open up your browser console, you'll be blown away ๐คฏ! Let's see what's happening there ๐
As you can see, the first container is rendered as a <div>
element since we didn't pass any as
prop to it, but the second element is rendered as <article>
element, because we passed as="article"
to it. Seriously, how cool is that?
Let's add the finishing touch to the Container
component and wrap this up! Since we can render the Container
as any HTML element, therefore there is a wide range of properties that we can pass to it, but adding these properties one by one is too time-consuming and hard to maintain. Instead, we can use JavaScript spread operator to pass the remaining props to the Container
. Add the following code to components/Container.jsx
:
// ./components/Container.jsx
export const Container = ({
as: Element = 'div',
children,
className = 'bg-secondary',
...rest
}) => {
return (
<Element {...rest} className={`custom-container ${className}`}>
{children}
</Element>
)
}
The {...rest} in the code is a shorthand way of passing all the remaining properties of the Container
component (that are not specifically defined in the destructuring assignment) as props to the Element
component. This allows the Element
component to receive additional props that the Container
component may not have specifically handled or defined.
Now let's try to add an id
to one of our containers and see what happens.
// ./App.jsx
import { Container} from './components'
function App() {
return (
<div>
<Container id="yay">
<p>this is container 1</p>
</Container>
<Container className="bg-primary" as="article">
<p>this is an article</p>
</Container>
</div>
)
}
Open up your browser console and check this out ๐ฅ
Although we haven't specified any id
prop on Container
, but it still renders the Container
with a correct id
using {...rest}
.
Now you have a reusable Container
component which you can use across your whole application or possibly across another projects!
Button Component
Now that we've successfully completed our Container
component, it's time to move on to the next step and create a reusable button component. To begin with, just add the following code :
// ./components/Button.jsx
export const Button = ({ onClick, children }) => {
return (
<button className="bg-primary" onClick={onClick}>
{children}
</button>
)
}
There is nothing going on here that you're not familiar with it already. We receive a children
prop and render it between <button>
tag, then we pass a onClick
prop which is a callback function. In JavaScript, a callback function is a function that is passed as an argument to another function, and is called when that function completes its task. we want to run this callback function when a user clicks on this Button
component, also we added a custom CSS class name to it so things gonna look cool! Now let's try this out and see what happens :
// App.jsx
import { Button, Container } from './components'
function App() {
return (
<div>
<Container id="container">
<h2>This is a heading !!!</h2>
<Button onClick={() => console.log('it works')}>
Click me baby !
</Button>
</Container>
</div>
)
}
Clicking on this button would trigger the onClick
event, which in turn would execute the function console.log('it works')
. You can actually open up your browser console and switch to Console tab to see if it works or not :
So far so good ๐! Now that the core functionality is there, we can add some variations to this Button
and spice things up a little bit ๐ฅ! Open up components/Button.jsx
and add the following code to it:
export const Button = ({
onClick,
children,
className,
primary,
danger,
disabled,
}) => {
return (
<button
className={`${danger && 'bg-danger'} ${
primary && 'bg-primary'
} ${className}`}
onClick={onClick}
disabled={disabled}
>
{children}
</button>
)
}
We've just added a few props here ๐! className
(custom CSS classes to be applied to the button), primary and danger (boolean values that determine the button's background color), and disabled (a boolean value that determines if the button is disabled). The component uses the &&
operator to conditionally apply the bg-danger
and bg-primary
classes to the button element based on the values of the danger
and primary
props respectively. The className
prop is also passed to the button element, so you can add any custom CSS classes you want.
Let's try it out in the App.jsx
:
import { Button, Container, Input } from './components'
function App() {
return (
<div>
<Container id="container">
<h2>This is a heading !!!</h2>
<Button onClick={() => console.log('blue is clicked')} primary>
Click me baby !
</Button>
<Button onClick={() => console.log('red is clicked')} danger>
Click me baby !
</Button>
<Button disabled>Click me baby !</Button>
</Container>
</div>
)
}
Now open up your browser, you must see the following page :
All of these are variations of the same component! You can easily modify this and add more variations and features to this Button
, but for the learning purposes, we are going to keep this the way it is.
Input Component
You are making fantastic progress ๐ฅ! The Input
component is going to be the last UI component we're going to build together, after that you should comfortable with creating your own reusable components ( If you're not already ๐
).
Open up components/Input.jsx
and add the following code to it:
export const Input = ({ label, placeholder, type, value, id, onChange }) => {
return (
<div className="input">
<label htmlFor={id}>{label}</label>
<input
placeholder={placeholder}
id={id}
type={type}
value={value}
onChange={onChange}
/>
</div>
)
}
There is nothing crazy going on here, Input
takes in several props like label
, placeholder
, type
, value
, id
, and onChange
.
It returns a block of HTML that includes a label and an input field. The label
prop is used to render the label text and id
prop is used to associate the label with the input field. The placeholder
prop is used to show a placeholder text in the input field. The type
prop is used to set the type of the input field (e.g. text, number, etc.). The value
prop is used to set the initial value of the input field. The onChange
prop is used to handle any changes made to the input field by calling a function passed as a prop.
Now we're going to spice things up a little bit and add useState
to the game ๐. Open up your App.jsx
and update it with the following code:
import { useState } from 'react'
import { Button, Container, Input } from './components'
function App() {
const [name, setName] = useState()
const handleNameChange = event => setName(event.target.value)
return (
<div>
<Container as="div">
<h2>{name || 'No name provided !'}</h2>
<p className="text-secondary">
This is subtitle for the container ! how cool is that?
</p>
<Input
label="Name"
placeholder="write something ..."
id="name"
type="text"
value={name}
onChange={handleNameChange}
/>
</Container>
</div>
)
}
export default App
Whenever a user types in the input field, the handleNameChange
function is called within the Input
which sets the name
state with the updated value of the Input
. The name
state is then used to render the heading, which displays the value of name
if it's not an empty string or displays a default message "No name provided!" if name
is empty.
If you run this code and open up your browser, although it'll be working correctly, you may notice a weird error in your browser console:
This warning occurs when a component is attempting to convert an uncontrolled input field to a controlled input field.
An uncontrolled input field is one where its value is initially set to undefined
or null
, and it's then allowed to be updated freely by the user.
On the other hand, a controlled input field is one where its value is explicitly set by the component's state or props, and any updates to it are handled by a callback function.
The warning is often caused by the value of the input field being changed from undefined to a defined value. To fix this, just make sure to pass an empty string as an initial value to your state
:
const [name, setName] = useState('')
Putting it All Together
For the final state of our app, and to make things more exciting, let's add a Button
to our app which can reset the Input
value and therefore the Container
title:
import { useState } from 'react'
import { Button, Container, Input } from './components'
function App() {
const [name, setName] = useState('')
const handleNameChange = event => setName(event.target.value)
return (
<div>
<Container as="div">
<h2>{name || 'No name provided !'}</h2>
<p className="text-secondary">
This is subtitle for the container ! how cool is that?
</p>
<Input
label="Name"
placeholder="write something ..."
id="name"
type="text"
value={name}
onChange={handleNameChange}
/>
<Button primary onClick={() => setName('Button is clicked')}>
Click me baby !
</Button>
</Container>
</div>
)
}
When the Button
is clicked, the name
state value is set to "Button is clicked". which displays the updated name
value in the header and also reset the Input
value to "Button is clicked".
After all said and done, let's add a default subtitle to our Container
as I promised in the beginning ๐:
// components/Container.jsx
export const Container = ({
as: Element = 'div',
children,
className = 'bg-secondary',
...rest
}) => {
return (
<Element {...rest} className={`custom-container ${className}`}>
{children}
<p>and this is a default text !</p>
</Element>
)
}
Now each time we use the Container
, it will have a default subtitle at the bottom. This is what our app looks like in the final stage of development ๐:
Conclusion
In conclusion, creating reusable React components can save you a lot of time and effort in your development process. By breaking down complex UI elements into smaller, reusable components, you can improve code reusability and make your codebase more maintainable.
In this blog post, we have learned how to create three different types of reusable React components and how to use them together to build a more complex UI element. I hope this post has been helpful in improving your React skills.
Remember, the key to creating effective reusable components is to make them modular, customizable, and easy to use. With a little practice, you'll be able to create high-quality reusable components that will improve your React projects and make your development process more efficient.
Thank you for reading! If you have any questions or feedback, please feel free to leave a comment below.