loading...
Cover image for An Introduction to Reusable Components and how to create Typography Component

An Introduction to Reusable Components and how to create Typography Component

nghiemthu profile image Thu Nghiem ・6 min read

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

Alt Text

Design On Figma

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-${variant}`]: variant,
        [`typography--color-${color}`]: 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:

Button Component

Alt Text

Input Component
Alt Text

Do you have some questions? Free feel to leave me a comment 😁

🐦 Thu Nghiem Twitter
🐦 Devchallenge.io Twitter
🔥 Devchallenges Website

Posted on by:

nghiemthu profile

Thu Nghiem

@nghiemthu

Creator of devchallenges.io, Software developer at Telia Finland

Discussion

pic
Editor guide
 

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 that

if (variant) {
return variantsMapping[variant];
} else {
return "p";
}
 

Ok 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.