DEV Community πŸ‘©β€πŸ’»πŸ‘¨β€πŸ’»

Cover image for The Making of A React Component Library
Avi Avinav
Avi Avinav

Posted on

The Making of A React Component Library

A component library is one of the coolest things a web developer can make, but if you don't know how to make one, let me guide you a bit.

Before we begin I would like to tell you, that I am not a pro at making component libraries, it's just from my experience developing a component library before.

Prerequisites

Before you get started, these are the following you should know:

  • Basics of React

  • Typescript (Optional but preferred, you can use Javascript too)

Why make a component library?

A react component library can be useful in many ways, you can use it in your projects as you scale and want uniformity or you can make your library open-source for everyone to use and contribute.

Tools Required

  • React - since, this is a tutorial about a react component library

  • Typescript - this is optional but recommended

  • Storybook - it's fine if you haven't heard of it, don't freak out. We will be using this to preview our components

Getting Started

There are a lot of ways we can set up our library, if you want to make several packages in the same organization I would recommend using a monorepo but for the sake of this tutorial we will be setting up a very simple library from scratch.

So, first of all, go ahead and make a package.json file:

npm init
Enter fullscreen mode Exit fullscreen mode

Once, you have initialized the project, we will start installing our dependencies. Unlike, in a normal project where you would just install the dependencies directly with npm i [dep-name], here we will be using dev-dependencies and peer-dependencies.

Dev-dependencies are the dependencies that do not get packaged in the bundle and do not contribute to the size of our library, it is a dependency that you only require during development.

Peer dependencies basically tell you that you need to have a certain package installed to use the library, for example, if I have a library named avi-lib and it has peer dependencies as react & react-dom, then if I want to use avi-lib in my project I need to have react & react-dom already installed as dependencies in the project.

Now go ahead and install react , react-dom, tyepscript, & @types/react, @types/react-dom as devDependencies.

yarn i -D react react-dom typescript @types/react @types/react-dom
Enter fullscreen mode Exit fullscreen mode

Add react & react-dom as peer-dependencies with appropriate versions:

{
    ...
    peerDependencies: {
        "react": ">=16.0.0",
        "react-dom": ">=16.0.0"
    }
    ...
}
Enter fullscreen mode Exit fullscreen mode

Next, initialize a tsconfig file:

npx tsc --init
Enter fullscreen mode Exit fullscreen mode

In the tsconfig.json file, go ahead and enable jsx with "jsx": "react" option.

Making the Component

Once this is done make a directory src or whatever you wish to name it. This directory will contain all code.

Inside src, make another directory components and create a file Button.tsx. Now, open the file and write out a simple button:

import React from 'react';

export interface ButtonProps {
  children: React.ReactNode;
  onClick: () => void;
}

export const Button = ({ children, onClick }: ButtonProps) => {
  return <button onClick={onClick}>{children}</button>;
};
Enter fullscreen mode Exit fullscreen mode

Now inside the src directory, create a index.ts file, and write out the following:

export { Button, ButtonProps } from "./components/Button"
Enter fullscreen mode Exit fullscreen mode

Storybook

Storybook is an amazing tool that is almost essential for component libraries, it helps you out with previewing your components adding documentation for them, and even lets you host the components for others to test. For the sake of this article, we will be primarily using it for testing our components.

To setup storybook in your library, run the following:

npx storybook --init
Enter fullscreen mode Exit fullscreen mode

Storybook automatically detects that this is a react library and installs it accordingly. After it has done installing you will see a .storybook directory and a src/stories directory. You don't really have to do anything with the .storybook directory, you will use your components in the src/stories directory.

Inside the src/stories directory you will see a bunch of stuff, but don't worry about it, it is all just mock data. You can go ahead and empty it leaving only the Button.stories.tsx. Now you will see a lot of things in this file go ahead and empty it and type in the following:

import React, { useState } from 'react';
import { storiesOf } from '@storybook/react';

import { Button } from '../';

const stories = storiesOf('Button', module);

stories.add('Button', () => {
  const [value, setValue] = useState('Hello');
  const setChange = () => {
    setValue(value === 'Hello' ? 'Bye' : 'Hello');
  };

  return <Button onClick={setChange}>{value}</Button>;
});
Enter fullscreen mode Exit fullscreen mode

Now go ahead and check it out, run the following in your terminal:

npm run storybook
Enter fullscreen mode Exit fullscreen mode

This will open a browser window on localhost:6006, and you can see our little button working, its value changes on being clicked.

That's about it, now go ahead and add your own magic to the button, make more components, and similarly, you can even create hooks.

Documentation

It is a good idea to document things about your components in your library in a README file or on a docs website, it helps out anyone using your library. For the sake of this tutorial, we will not be writing documentation because it only contains a single button component.

Bundling

Once you are done with making your components, let's bundle our library.

It's a good idea to bundle because it helps you produce a single file. Some people prefer dragging and dropping a single file in their projects. A single file can also be used for CDNs.

To bundle your component you will have to use a bundler. There are a lot of options out there but here we will be using tsup because tsup doesn't require a configuration for our use case.

To install tsup, do the following:

yarn add -D tsup
Enter fullscreen mode Exit fullscreen mode

This installs tsup as a dev-dependency, now inside your package.json file add a script:

{
    ...
    scripts: {
        ...
        "build": "tsup src/index.ts --dts"
    }
    ...
}
Enter fullscreen mode Exit fullscreen mode

Now, go ahead and run yarn build, this will create a index.js file inside the dist directory. If you observe, we added a --dts in our build script, this will generate the type definitions for our project in index.d.ts.

Publishing

Now that all is done, it's time to show what you have built to the world (or not if it's for personal use). Before publishing, go to your package.json and you will find a few things, choose a name for your library and add an appropriate version (you can use semantic versioning if you don't know how to), a description of what it does, and a few keywords to make your library discoverable.

Now add a .npmignore file, this is very similar to .gitignore, this file will contain what you don't want to publish along with your library, for example, node_modules. Though this file is optional even if you don't include it, the .gitignore file will be used instead.

If you are not logged in to your npm account do npm login and you will have to enter your username and password. In the case, you don't have an npm account you can go to https://www.npmjs.com/ and register for one.

Once you have done that just simply run this command in the terminal:

npm publish
Enter fullscreen mode Exit fullscreen mode

Once it finishes without any errors, Congrats! Your library has been published. Now you (and others too) can use your library in your projects.

Tips

  • Try to use typescript

  • Add documentation in your README & if you want, a docs website

  • I have not done it in the article but use git and make changes on a different branch than main

  • Read more on Storybook, its templates, and documentation

More Resources

Hope you enjoyed the read and learned something from it. In the case, you want to add something or find a mistake and wish to point it out, please feel free to do so in the comments.

Top comments (29)

Collapse
brense profile image
Rense Bakker

Do be careful when creating a component library that is going to be used within a company. Some general tips: Make sure you follow React design patterns like composition and read up on the open/closed principle, otherwise you're going to create a lot of frustration for your fellow dev. If you're extending an existing UI component library, make sure you follow the same pattern and dont create black boxes for the components that you're extending.

Collapse
aviavinav profile image
Avi Avinav Author

Thanks for adding that!

Collapse
dondaniel profile image
Don1011

Hi, at the time of posting this comment, npx storybook --init doesn't work and based on the documentation, it's because a project creation framework wasn't used. So simply using npx sb init worked fine.

Collapse
aviavinav profile image
Avi Avinav Author • Edited on

Thanks for pointing that out!

Collapse
femi_dev profile image
Femi Akinyemi

Why do you strongly recommend typescript over JavaScript ?

Collapse
aviavinav profile image
Avi Avinav Author

Specifically for a component library, I would recommend using typescript because it provides sort of a built-in documentation.

Let's say the Button component that we used in the article also had a size prop which is only supposed to accept values: lg, md, or sm (standing for large, medium and small respectively), then I can just define my component's props like this:

export interface ButtonProps {
  children: React.ReactNode;
  onClick: () => void;

  /** size of the button, tells what size of button do you want */
  size: 'lg' | 'md' | 'sm';
}
Enter fullscreen mode Exit fullscreen mode

This way when the user actually uses our component inside their project if they put any other value for the size prop besides lg, md, or sm, they will recieve a warning. If you would notice, I also added a comment above the size prop, this helps as sort of a documentation. You can see this happening in the image below:

As you can see, my editor shows me there's a problem because large is not an acceptable value for the size prop. Also you can see when I hover over the size prop it shows me the acceptable values and the comment we wrote before.

This also provides better intellisense as you can see in the image below:

By default all the props you give to your components (in typescript) will be complusory, to make them optional you can just add a ?, like this:

export interface ButtonProps {
  children: React.ReactNode;
  onClick?: () => void;    // look here

  /** size of the button, tells what size of button do you want */
  size: 'lg' | 'md' | 'sm';
}
Enter fullscreen mode Exit fullscreen mode

This makes the onClick prop optional so now it won't cause an error if onClick is not specified during usage, but if you don't specify the size prop or add children during usage it will show a warning as they are compulsory (do not have a ?).

Collapse
jasonrundell profile image
Jason Rundell

An alternative to TypeScript is PropTypes npmjs.com/package/prop-types

Thread Thread
aviavinav profile image
Avi Avinav Author

I have heard of it but haven't tried it

Collapse
lozio1992 profile image
lozio

thanks, it helps me a lot. Can I translate it into Chinese and share it to more people on juejin.im ?

Collapse
aviavinav profile image
Avi Avinav Author

Sure, go ahead, just mention the original article too. Let me know once you have done it. Thanks!

Collapse
lozio1992 profile image
lozio
Thread Thread
aviavinav profile image
Avi Avinav Author

Awesome! Thanks!

Collapse
posandu profile image
Posandu |πŸ”₯|

Hey, there's a typo in your article title. It should be 'Library' but you've written 'Librabry'

Collapse
aviavinav profile image
Avi Avinav Author

Thanks for pointing that out, I was so focused on looking at the body of the post that I forgot to check the typo in the title.

Collapse
bosz profile image
Fongoh Martin T.

This is good.

Collapse
aviavinav profile image
Avi Avinav Author

Thanks!

Collapse
pavlov profile image
Alexey Pavlov

Nice article. I wrote a similar article recently

Btw, nx is also an option for managing monorepos

Collapse
aviavinav profile image
Avi Avinav Author

Haven't tried nx before, but will try it out

Collapse
lucianodiisouza profile image
Luciano dii Souza

This guide looks pretty good! Thanks for sharing your knowledge!!

Collapse
aviavinav profile image
Avi Avinav Author

Glad you liked it!

Collapse
shreelimbkar profile image
Shrivardhan Limbkar

check out this - TSDX - Zero-config CLI for TypeScript package development tsdx.io/

Collapse
aviavinav profile image
Avi Avinav Author

I did know about it and have tried it in the past but from what I know it's no longer being maintained, so I wouldn't recommend using it.

Collapse
shreelimbkar profile image
Shrivardhan Limbkar

oh..okay. make sense.

Collapse
devangtomar profile image
Devang Tomar

That was a nice read! Liked, bookmarked and followed, keep the good work! πŸ™Œ

Collapse
aviavinav profile image
Avi Avinav Author

Thanks!

Collapse
aviavinav profile image
Avi Avinav Author

I hope you like it. Please feel free to advise me on how I can improve.

Collapse
weptwithoutwit profile image
⚫️ aha hah • Edited on

web2worksonmymachine

🌚 Browsing with dark mode makes you a better developer by a factor of exactly 40.

It's a scientific fact.