Skip to main content

React Component Libraries & Storybook

As you continue on your journey as developers, there will be times when you will want to use the same components across many of your projects. Recreating the same components over and over again would be tedious, monotonous and frankly, a waste of time. That's where component libraries come in.

There are many component libraries out there that developers like you can install into their programs, for free or for a fee, depending on the library. You can also create your own, deploy them to use throughout many projects, or make them available to other developers to use!

Prerequisites

  • Intro to React
  • Functional Components
  • Class Components

Objectives

By the end of this, developers should be able to:

  • Create a React Component Library
  • Use Storybook to create stories and implement a UI for the component library

Component Libraries

Component libraries can be used to save components that you want to use multiple times, in multiple projects or applications. Typically consisting of styled UI components, they can enhance the overall aesthetic of your applications, give them a polished and consistent look and save you a lot of time, eliminating the need to write the same code for the same components as many times as you may want to use them. How DRY is that?

In the real world, it's likely that you may work with a design team, for a company that has branding guidelines in place. Meaning, what fonts, colors and tons of other styles to use in their materials, including a web or mobile application. A component library comes in handy here - you can simply create a branded component library to implement all the time. More likely than not, a component library may already be created for you.

Storybook

Today, we will use Storybook to help us create a React component library.

Storybook is a user interface development environment for UI components. It enables developers to create and showcase components independently and interactively in an isolated development environment. While the component library is still written in React, Storybook is a dependency that allows us to run it outside of a main React application so users can develop UI components in isolation without worrying about app specific dependencies and requirements.

In short, Storybook is a collection of stories. Each story represents a single visual state of a component.

Storybook

Check out the Storybook Documentation here! It's important to note that in Storybook 5.2, a new format called Component Story Format (CSF) was released. However, this format is not yet supported by React Native. For this reason, we are covering the StoriesOf API, which isn't the newest format but by far the most popular.

We Do: Getting Started

Clone this repo, change into the new directory and install all dependencies by running npm install.

In the project directory, run the following commands:

npx sb init

This command will install Storybook as a dependency on your project.

In package.json, you can see the following script command, which we will be using to start our Storybook in a few minutes:

{
"scripts": {
"storybook": "start-storybook"
}
}

Here, we have a simple functional component for a button.

Button.js
// Button.js

import React from "react";

// This is a functional component - just set up a little differently as an arrow function!
const Button = (props) => <button>{props.label}</button>;

export default Button;

What about this component looks familiar? What looks different?

Next, let's take a look at the stories file. What's happening here?

Button.stories.jsx
// Button.stories.js | Button.stories.jsx

import React from "react";

import { Button } from "./Button";

export default {
component: Button,
title: "Components/Button",
};

export const Primary = () => <Button primary>Button</Button>;

First, we import the modules and files we need. Here, we are importing React and our Button component.

I Do: Add Styles

This button doesn't have much to it. Let's add some styles to make it look like the following button mock:

Style is another one of the properties that can go inside the .add method! First let's declare a style object with all of the style we want to add - backgroundColor, color, borderRadius, etc. We will pass a style property into the Button component in the callback function so that we can call on it as props in the next step.

Button.stories.jsx
// Button.stories.js | Button.stories.jsx

import React from "react";

export default {
component: Button,
title: "Components/Button",
};

//👇 We create a “template” of how args map to rendering
const Template = (args) => <Button {...args} />;

//👇 Each story then reuses that template
export const Primary = Template.bind({});

Primary.args = {
primary: true,
label: "Button",
style: {
backgroundColor: "#0069D9",
color: "white",
borderRadius: "2px",
padding: "5px 25px",
border: "#0069D9",
},
};

Next, we'll update the Button component to call on style prop that we just passed in.

Button.js
// Button.js

const Button = (props) => <button style={props.style}>{props.label}</button>

Let's head back to the Storybook UI and see how our button component has changed!

You Do: Add More Buttons

If I want to create a new story, I can simple add it beneath the one I just made by export using Template.bind({})!

Button.stories.jsx
// Button.stories.js

export const Danger = Template.bind({});
Danger.args = { ...Primary.args, label: "Danger" };

Now it's your turn to give it a try! Update your Button stories file to meet the following requirements:

  1. Add styles to the Danger component.
  2. Add two more components: Warning and Success and add styles to them.

I Do: Add Styles with a Class

Adding a style property is a great way to quickly implement styles on your component or add a story that is drastically different than the others in the group. More often, you'll see styles implemented with classes.

You'll notice a Button.css file in you the Button directory in your file system. Let's import it into our Button.js file so that we can use it. There are some style that are consistent across the buttons we have created so far. We can add them to the button selector in CSS to keep our code nice and DRY.

We'll also add the unique styles of the Primary button to a class selector called .button-primary.

Button.css
/* Button.css */

button {
border-radius: 2px;
padding: 5px 25px;
}

.button-primary {
background-color: #0069D9;
color: white;
border: #0069D9;
}

Next, we'll add an additional property to our Primary Button story. Let's set the type to 'primary'. We will leverage this property to dynamically implement CSS using a class.

Button.stories.jsx
// Button.stories.js

export const Primary = Template.bind({});

Primary.args = {
primary: true,
label: "Button",
type: "primary
};

Now we'll update our component in Button.js to implement the class using string interpolation. Basically, we are telling our component to implement the css based on incorporating type that we set in our story into our class name. Like this:

Button.js
// Button.js

<button className={`button-${props.type}`}>{props.label}</button>

You Do: Refactor Using Classes

Refactor your code using classes to style each component instead of the style property.

I Do: Conditional Styling

There's an even more dynamic way to implement styles into your component library. We can add condidtions to our Button component in Button.js to incorporate more styles. This is going to be really helpful when we have components in our library that vary slightly from one another. For example, what if we want to have a button component that looks almost the same, just a little larger?

Let's update the CSS first to add more padding.

/* Button.css */

.button-large {
padding: 10px 50px;
}

Now let's add a new story for a Large Primary Button to our stories and give it a large property..

Button.stories.jsx
// Button.stories.js

export const LargePrimary = Template.bind({});
LargePrimary.args = {
...Primary.args,
label: "Large Primary Button",
large: true,
};

Finally, we'll add some conditions to our Button component.

Button.js
// Button.js

const Button = (props) => {
// Declare a classList variable and set it to an empty string
let classList = "";

// Create an array of all of the story/component types you want to be
// included in your component library
let types = ["primary", "danger", "success", "warning"];

// Add a conditional statement that checks for the type and updates the
// classList variable based on their existence.
if (types.includes(props.type)) {
classList += ` button-${props.type}`;
}

// Add another conditional statement to check for additional properties (such as large)
// and add to the classList variable based on this condition evaluating to true
if (props.large) {
classList += ` button-large`; // Note the spacing here since we are adding to the string!
}

// Give the button's class a value of classList
return <button className={classList}>{props.label}</button>;
};

What's going on here?

First, we are declaring a classList variable and set it to an empty string. Based on the conditions we set up, we can update its value to implement the classes we want.

Next, we have an array that includes all of the types in our stories file. We can check to make sure the types are included before adding to the classList so that a component that we don't need doesn't have a class added to it unnecessarily.

Our second condition checks to see if a large property exists within a story. If it does, it will add button-large to the classList, which correlates to the .button-large selector we already have in our Button.css file.

Finally, we can update our button's class to have a value of the classList variable, which will now include all of the classes we need based on the properties we set up in our stories. Let's go back to our Storybook UI and see how it turned out!

You can add even more styles using conditions! The possibilities are endless! Component libraries are built to make our lives as developers much easier. If you implement the logic into your component, you can seamlessly add more stories, or components, to your library following the same conventions and built your UI with the styles you want.

You Do: Refactor Using Conditionals

Refactor your code using properties, classes and conditional statements to style each component.

  1. Add a Large version of another Button component.
  2. Add a hover feature to a Button component, where the button's background color gets slightly darker when the user hovers their mouse over it.
  3. Add an Outline version of one of the Button components, where the background color is slightly lighter but the border color remains the same.

Event Listeners

Some UI components will require some kind of user interaction - like a button! In this case, you'll want to pass an event listener into the button element as props to make the implementation of callback functions easier after you have imported the component library into a project.

Button.js
// Button.js

const Button = (props) => (
<button className={classList} onClick={props.onClick}>
{props.label}
</button>
);

In this scenario, an instance of the Button component will have the ability to trigger an onClick method that would likely be passed down from a parent element within an actual project or application.

Additional Resources

LICENSE

  1. All content is licensed under a CC­BY­NC­SA 4.0 license.
  2. All software code is licensed under GNU GPLv3. For commercial use or alternative licensing, please contact legal@ga.co.