One reason why React has become so popular is because of its reusable components. During the last few years, the concept of design system has also become popular among web developers.
I see many people making a mistake when learning React: they go straight to learning Redux and start building complex applications. They forget about learning the basics.
Because of that, they don't know why they are using React. Is it really useful or is it just trendy?
In this article, we will look at what reusable components are, and why they are one of the best ways to get started with React. And we will build a typography component together.
What are reusable components?
When a component is used more than once, it is reusable. For example in a list, we don't want to make a list item more than once. So we have to make that component reusable.
But reusable components are more than just an item inside a list. Some examples of reusable components are button and input. These are global components as they can be used anywhere.
Some are reusable but it's not necessary that they can be used everywhere. One example is that <tr>, <th>, <td>
are reusable in <table>
but cannot (should not) be used anywhere else.
You might already use reusable components. For example, if you are using BEM naming, you can see that Block names are global components, and Element names are scoped components.
Reusable components get more exciting when it comes to React.
Why you should care about them
At this point, you might already see the benefits of reusable components. But here is a list:
Efficient
You no longer have to spend your time thinking about pixels and doing the same things over and over again. You can save time by relying on your reusable components. This means that you have more time to improve quality, get your app done faster, and reduce costs.
Consistent
Having consistency in your application is more important than you might think. As your users start to use your application, they will start to learn about it. They will start to find a pattern.
An application with consistency will help your users find information faster and with less confusion.
Maintainable
Let's say that your designers decide to change the padding in the buttons. Now you have to search for every place that has <button>
, go to every CSS file, and try to find where the padding is.
That is a lot of work. So instead of doing that, if you have reusable components you just need to change it in one place.
Avoids duplicated code
Duplicated code is not a bad thing, as it makes your app more flexible. But the code that you have to write again more than three times is not a good thing. Using reusable components helps you avoid copying your code every time.
How to make a good reusable component
Building a reusable component can be tricky. Here are a few things you want to look out for:
Component should be dumb
For example, the Button should not know the current theme. Instead, the application should tell the Button which theme it is.
Incorrect
const Button = ({ children, ...props }) => {
const { state } = useContext(AppContext);
return (
<button
className={cn({
"button--theme-dark": state.theme === "dark",
})}
{...props}
>
{children}
</button>
);
};
In this example, we get the global state from AppContext
in Button
component. This means we have created a dependency between the Button
and the Application
. Therefore, the component is only reusable in the Application Context and we want to avoid this.
Correct
const Button = ({ theme, children, ...props }) => {
return (
<button
className={cn({
"button--theme-dark": theme === 'dark',
})}
{...props}
>
{children}
</button>
);
};
The button in this example is independent and can be used in any application. This is what we want to do.
Scalable
The component should be flexible enough that you can add more configuration easily later on.
In this example, instead of having hasPrimaryColor
as a boolean, it should have a prop: backgroundColor="primary"
. Some other props should not be boolean
like: size
, varient
,...
Incorrect
const Button = ({ hasPrimaryColor, children, ...props }) => {
return (
<button
className={cn({
"button--color-primary": hasPrimaryColor,
})}
{...props}
>
{children}
</button>
);
};
Correct
const Button = ({ color, children, ...props }) => {
return (
<button
className={cn({
"button--color-primary": color === "primary",
})}
{...props}
>
{children}
</button>
);
};
Simple
The more complex the component is, the harder to maintain it. You might hear the terms: Stateless Components and Stateful Components, most of the time Stateless Components are simpler than Stateful Components.
But what are the differences? Well.. it deserves a separate post. But basically, if you can move the logic outside the component and keep it dumb, then you should do it 🙂
Building a Typography Component
User stories
- As a user, I can choose to have 10 variants: h1, h2, h3, h4, h5, h6, subheading 1, subheading 2, body 1, and body 2
- As a user, I can choose to have primary or error colors
Step 1: Create-react-app and install classnames
Let's create a React app and install classnames. Classnames
will allow you to have multiple classes conditionally.
npx create-react-app typography
cd typography
npm i classnames
Step 2: Import font
You can go to Google Font to choose the ones you wish. In our case, we use Ubuntu.
You can import by using <link>
tag inside <head>
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@400;500&family=Ubuntu+Mono&display=swap" rel="stylesheet">
or you can import in your css file
@import url('https://fonts.googleapis.com/css2?family=Poppins:wght@400;500&family=Ubuntu+Mono&display=swap');
Step 3: Use the font and reset the default styling
Let's reset some of the default styles and use our font. By resetting the default, we are free to give it our own style without knowing what the default values are.
In our cause, let's remove the default padding and margin. Some other components might have border
, background-color
, text-decoration
,..
body {
margin: 0;
font-family: "Poppins", sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
,
::after,
*::before {
box-sizing: inherit;
}
h1,
h2,
h3,
h4,
h5,
h6,
p {
margin: 0;
padding: 0;
}
Step 4: Create a typography component
Always remember to pass ...props
to your component, so that we don't lose the default attribute.
import React from "react";
import cn from "classnames";
import "./typography.css";
// Defining the HTML tag that the component will support
const variantsMapping = {
h1: "h1",
h2: "h2",
h3: "h3",
h4: "h4",
h5: "h5",
h6: "h6",
subheading1: "h6",
subheading2: "h6",
body1: "p",
body2: "p",
};
// Create a functional component that take
// variant: the selected html tag
// color: the selected color
// children: the node passed inside the Component
// ...props: the default attribute of the Component
const Typography = ({ variant, color, children, ...props }) => {
// If the variant exists in variantsMapping, we use it.
// Otherwise, use p tag instead.
const Component = variant ? variantsMapping[variant] : "p";
return (
<Component
className={cn({
[typography--variant-</span><span class="p">${</span><span class="nx">variant</span><span class="p">}</span><span class="s2">
]: variant,
[typography--color-</span><span class="p">${</span><span class="nx">color</span><span class="p">}</span><span class="s2">
]: color,
})}
{...props}
>
{children}
</Component>
);
};
export default Typography;
Step 5: Add styling to your component
The last step is to give style to our component. This code is straightforward, we add different font-size
and font-weight
to each variant option and color
to color option.
.typography--variant-h1 {
font-size: 6rem;
font-weight: 500;
}
.typography--variant-h2 {
font-size: 3.75rem;
font-weight: 500;
}
.typography--variant-h3 {
font-size: 3rem;
font-weight: 500;
}
.typography--variant-h4 {
font-size: 2.125rem;
font-weight: 500;
}
.typography--variant-h5 {
font-size: 1.5rem;
font-weight: 500;
}
.typography--variant-h6 {
font-size: 1.25rem;
font-weight: 500;
}
.typography--variant-subheading1 {
font-size: 1rem;
font-weight: 500;
}
.typography--variant-subheading2 {
font-size: 0.875rem;
font-weight: 500;
}
.typography--variant-body1 {
font-size: 1rem;
}
.typography--variant-body1 {
font-size: 0.875rem;
}
.typography--color-primary {
color: #f2994a;
}
.typography--color-error {
color: #eb5757;
}
Challenge
The component is not totally complete. I challenge you to add more props like: align
, display
, marginButton
,...
Result
You can find the source code here if you want to check it out.
Conclusion
After making Typography component, we can see that making reusable components can be difficult and usually saves you a lot of time in the future. It is also a good way to get started learning React or Vue.
Next time, when working with React, don't be lazy and simply copy code from other places. If you think it should be a component, make it one. It will help you out a lot.
Here are 2 challenges to get started creating Reusable components and learning React:
Do you have some questions? Free feel to leave me a comment 😁
🐦 Thu Nghiem Twitter
🐦 Devchallenge.io Twitter
🔥 Devchallenges Website
Top comments (8)
Nice, will try the challenges. Noob question... variant?variantsMapping[variant]:"p"; written this way will always output variantsMapping wheter it is in the mapping or not? I mean variant could be' any truthy value
Hi sorry for the late reply.
variant ? variantsMapping[variant] : "p"
means thatOk bit even "bingobongo" is truthy that would lead to undefined or error doesnt it?
yeah good points. it will return undefined and error in that case :) good catch. usually, it will have propTypes or typescript to prevent those. but yeah we can add more check to this. But this is an example and the component is not fully complete.
I highlighted this article from many others. Excellent style and description of various aspects of the work. I liked another article as well; you can check it out at the following link: how to make a typography.
Hello 👋. In my project I have primary and secondary buttons with global .btn class which I use on a and button tags, and I can easily change .btn class padding if the designer wishes, but I want to use Button component, because lots of project examples use that option. If not difficult, can you give other examples why to use Button component, what are the benefits?
nice
Great read, thank you ;)