• Get application security done the right way! Detect, Protect, Monitor, Accelerate, and more…
  • Have you ever tried to place all your UI components at a place in React?

    If you are new to the world of React, then you probably won’t.

    What is meant by that?

    See the react-beautiful-dnd examples.

    What you have seen in the examples are called stories. And the tool that’s used to create stories is called Storybook.

    Now, you have understood what we are going to talk about in this article. Without ado let’s explore.

    What is Storybook?

    Storybook is a user interface isolated development environment that provides a playground for your components. We can play with our components in different ways without running our main app. We can run the storybook in its port with the setup.

    It’s not limited to React. We can use storybook with most of the frontend frameworks like Vue, Angular, Mithril, Marko, Svelte, etc..,

    You can find more about the storybook here.

    What is a story?

    A story defines the rendered state of your component. If we take a common component, we can use it in different ways with props. We can write a story for each of those states.

    Let’s say we have a Button component.

    A button can exist in different states like disabled, loading, primary, secondary, small, large, medium, etc.., If we list down all the states, then it will be very difficult to move forward in the tutorial. I think you understand it. You will get it more when start working with the storybook.

    Stories

    You can see the stories of the button in different cases (Large, Medium, Small).

    Setting up Storybook in a Project

    We’ll setup a storybook in a react project.

    Let’s go.

    • Create a react project with the following command. You can name whatever you like.
    npx create-react-app storybook-demo
    • Now, install the storybook in your project with the following command.
    npx sb init

    We have completed the setup for the storybook.

    The storybook provides a separate server for us.

    How to start it?

    The storybook automatically adds a command in our script file. You can check it in package.json file inside scripts section. For the time being, run the following command to start the storybook server.

    npm run storybook

    Storybook will start a new server with port given in the scripts section of package.json file. It will automatically open the storybook in our default browser (same as the react server).

    You will see different stories in it by default. You can remove them if you don’t want or keep them for reference. As we discussed in the previous section, a button can have multiple states, you can see them in the storybook (not all states mentioned). We are going to write a large set of stories for the button in the last section of this tutorial.

    Explore different sections of storybook and get familiar with the different section. We are going to cover few of them in the tutorial.

    Let’s write our first story.

    Testing storybook

    We have seen the storybook running and some examples in it.

    • Create a folder called Button inside the src folder.
    • Create files called Button.jsx, Button.css and constants.js
    • Place the respective code from the below snippets in the files.

    Button.jsx

    import React, { Component } from "react";
    import PropTypes from "prop-types";
    
    import "./Button.css";
    
    import { buttonTypes, buttonVariants, buttonSizes } from "./constants";
    
    class Button extends Component {
        static defaultProps = {
            isDisabled: false,
            type: "filled",
            variant: "oval",
            size: "medium",
            backgroundColor: "#1ea7fd",
            textColor: "#ffffff",
        };
    
        static buttonTypes = buttonTypes;
        static buttonVariants = buttonVariants;
        static buttonSizes = buttonSizes;
    
        renderButton = () => {
            const {
                text,
                isDisabled,
                type,
                variant,
                size,
                backgroundColor,
                textColor,
                onClick,
            } = this.props;
            return (
                <button
                    onClick={onClick}
                    className={`default ${variant} ${size} ${
                        isDisabled ? "disabled" : ""
                    }`}
                    style={
                        type === buttonTypes.outline
                            ? {
                                  border: `1px solid ${backgroundColor}`,
                                  color: "#000000",
                                  backgroundColor: "transparent",
                              }
                            : {
                                  backgroundColor: `${backgroundColor}`,
                                  border: `1px solid ${backgroundColor}`,
                                  color: textColor,
                              }
                    }
                    disabled={isDisabled}
                >
                    {text}
                </button>
            );
        };
    
        render() {
            return this.renderButton();
        }
    }
    
    Button.propTypes = {
        text: PropTypes.string,
        isDisabled: PropTypes.bool,
        type: PropTypes.oneOf([buttonTypes.outline, buttonTypes.filled]),
        variant: PropTypes.oneOf([buttonVariants.oval, buttonVariants.rectangular]),
        size: PropTypes.oneOf([
            buttonSizes.small,
            buttonSizes.medium,
            buttonSizes.large,
        ]),
        backgroundColor: PropTypes.string,
        textColor: PropTypes.string,
        onClick: PropTypes.func,
    };
    
    export { Button };

    Button.css

    .default {
        border: none;
        cursor: pointer;
        background-color: transparent;
    }
    
    .default:focus {
        outline: none;
    }
    
    .disabled {
        opacity: 0.75; 
        cursor: not-allowed;
    }
    .small {
        font-size: 12px;
        padding: 4px 8px;
    }
    
    .medium {
        font-size: 14px;
        padding: 8px 12px;
    }
    
    .large {
        font-size: 16px;
        padding: 12px 16px;
    }
    
    .oval {
        border-radius: 4px;
    }
    
    .rectangular {
        border-radius: 0;
    }

    constants.js

    export const buttonTypes = {
        outline: "outline",
        filled: "filled",
    };
    
    export const buttonVariants = {
        oval: "oval",
        rectangular: "rectangular",
    };
    
    export const buttonSizes = {
        small: "small",
        medium: "medium",
        large: "large",
    };

    What’s that code?

    We have written a common component for Button that can be used in different ways. Now, we have a component that can have different states.

    Let’s write our first story by following the below steps.

    • Create a file called Button.stories.jsx
    • Import React and our Button component into the file.
    • Now, define a title or path our component stories. We will define it using the following code.
    export default {
       title: ‘common/Button’,
    }
    The above code will place all stories that are in the current file inside common/Button/ directory.
    • Export a button with mandatory props as follows.
    export const defaultButton = () => (
        <Button text=”Default Button” onClick={() => {}} />
    );

    We have completed our first story. Run the storybook with the following command and see the output.

    npm run storybook
    We’ll write more stories, in the end, don’t worry.

    How is it useful in Frontend development?

    What’s the main advantage of using a storybook?

    Let’s say we are working in a team of 10 members. And we need to check common components that everyone has written for the current working project.

    How can we do that?

    We have to go to every common component to check them. But, it’s time-consuming and not a preferred way for us. Here comes our new guest storybook.

    How to utilize it to overcome our problem?

    We can write stories for the common components (any UI components) using storybook. And whenever your teammate wants to check the common components of others, then they simply run the storybook server and will see all the UI components there as we have seen above.

    We can do a lot more with the rendered components in the storybook. Storybook has a concept called Addons that gives superpowers to our stories.

    Let’s say we have to check the responsiveness of the UI components in the storybook itself, we can use an addon called Viewport in the storybook. We will learn more about the addons in the coming sections.

    Working with Storybook

    In this section, we will write different stories defining different states of our common component Button.

    Writing stories is not so difficult. A story defines a state of a component. If you see the props of a component, then you will easily understand different use cases of the component.

    Let’s write some stories by giving optional props.

    export const largeButton = () => (
        <Button text="Large Button" onClick={() => {}} size="large" />
    );
    export const outlineSmallButton = () => (
        <Button
            text="Outline Small Button"
            onClick={() => {}}
            size="small"
            type="outline"
        />
    );
    export const rectangularLargeButton = () => (
        <Button
            text="Rectangular Large Button"
            onClick={() => {}}
            size="large"
            variant="rectangular"
        />
    );
    
    
    export const disabledButton = () => (
        <Button text="Disabled Button" onClick={() => {}} isDisabled={true} />
    );
    
    
    export const warningButton = () => (
        <Button
            text="Warning Button"
            onClick={() => {}}
            backgroundColor="orange"
        />
    );

    The above three stories define different use cases of our component Button. Now, it’s your turn to add some other cases of stories for our common component. Try to add disabledSamllRectangularButton, dangerButton, successDisabledButton, etc..,

    I am not going to provide code for the above cases. You have to write it on your own to understand it. You can see the complete stories code that we have written till now.

    import React from "react";
    
    import { Button } from "./Button";
    
    export default {
        title: "src/common/Button",
    };
    
    export const defaultButton = () => (
        <Button text="Default Button" onClick={() => {}} />
    );
    
    export const largeButton = () => (
        <Button text="Large Button" onClick={() => {}} size="large" />
    );
    
    export const outlineSmallButton = () => (
        <Button
            text="Outline Small Button"
            onClick={() => {}}
            size="small"
            type="outline"
        />
    );
    
    export const rectangularLargeButton = () => (
        <Button
            text="Rectangular Large Button"
            onClick={() => {}}
            size="large"
            variant="rectangular"
        />
    );
    
    export const disabledButton = () => (
        <Button text="Disabled Button" onClick={() => {}} isDisabled={true} />
    );
    
    export const warningButton = () => (
        <Button
            text="Disabled Button"
            onClick={() => {}}
            backgroundColor="orange"
        />
    );

    Now, you got a full grip on writing stories for a component.

    Let’s jump into the next section where we will learn about addons and how they boost our stories.

    Storybook Addons

    We’ll have multiple addons available by default. In the section, we will explore the most useful addons for our development.

    Let’s boost our Button stories.

    Controls

    Controls add a feature to give custom props to the component in the storybook itself. For our Button component, we can add controls to change the different props in the storybook.

    Let’s say we have to find out the best colour for the Button background colour. It will be time-consuming if we test it to check the background colour by giving one by one to the component. Instead, we can add a control that allows us to choose the different colour in the storybook. We can test the background colour in the storybook itself.

    Let’s see how to add controls to our Button stories.

    First, we have to define all the props below the title as follows.

    export default {
        title: "src/common/Button",
        argTypes: {
            text: { control: "text" },
            backgroundColor: { control: "color" },
            isDisabled: { control: "boolean" },
            size: {
                control: { type: "select", options: ["small", "medium", "large"] },
            },
            type: {
                control: { type: "select", options: ["filled", "outline"] },
            },
            variant: {
                control: { type: "select", options: ["oval", "rectangular"] },
            },
        },
    };

    Next, separate the props from the component and give them as args as follows.

    export const outlineSmallButton = (args) => (
        <Button {...args} onClick={() => {}} />
    );
    outlineSmallButton.args = {
        text: "Outline Small Button",
        size: "small",
        type: "outline",
    };

    You can see the controls at the bottom of the component preview window.

    Storybook Controls

    You can see the controls tab at the bottom of the component preview window. Play around it.

    Update all the stories as above. This is all more like knowing the syntax of the storybook addons. In the argTypes, we have used different types of controls. You can find all controls that are present in the storybook here.

    Updated button stories will look as follows.

    import React from "react";
    
    import { Button } from "./Button";
    
    export default {
        title: "src/common/Button",
        argTypes: {
            text: { control: "text" },
            backgroundColor: { control: "color" },
            isDisabled: { control: "boolean" },
            size: {
                control: { type: "select", options: ["small", "medium", "large"] },
            },
            type: {
                control: { type: "select", options: ["filled", "outline"] },
            },
            variant: {
                control: { type: "select", options: ["oval", "rectangular"] },
            },
        },
    };
    
    export const defaultButton = (args) => <Button {...args} onClick={() => {}} />;
    defaultButton.args = {
        text: "Default Button",
    };
    
    export const largeButton = (args) => (
        <Button {...args} onClick={() => {}} size="large" />
    );
    largeButton.args = {
        text: "Large Button",
    };
    
    export const outlineSmallButton = (args) => (
        <Button {...args} onClick={() => {}} />
    );
    outlineSmallButton.args = {
        text: "Outline Small Button",
        size: "small",
        type: "outline",
    };
    
    export const rectangularLargeButton = (args) => (
        <Button {...args} onClick={() => {}} />
    );
    rectangularLargeButton.args = {
        text: "Rectangular Large Button",
        size: "large",
        variant: "rectangular",
    };
    
    export const disabledButton = (args) => <Button {...args} onClick={() => {}} />;
    disabledButton.args = {
        text: "Disabled Button",
        isDisabled: true,
    };
    
    export const warningButton = (args) => <Button {...args} onClick={() => {}} />;
    warningButton.args = {
        text: "Warning Button",
        backgroundColor: "orange",
    };

    Actions

    Actions are events in JavaScript. We can click a button that is an event in JavaScript. We can do some actions on button click using the actions addon.

    With actions, we can test whether the events are working properly or not. Disabled button can’t be clicked and enabled button must be clickable. We can ensure it using the actions.

    Let’s see how to add action to the button click.

    We have given anonymous function to the onClick props previously. Now, we have to update it.

    • Import the action from the storybook addon using the following statement.
    import { action } from "@storybook/addon-actions";
    • Replace all the () => {} with the following statement.
    action("Button is clicked!")

    Now, go to the storybook and click a button. You will see the message printed under the actions tab that is beside the controls tab. The message won’t be printed if you click the disabled button as it is disabled.

    Storybook Actions

    We can use the action for different events like onChange, onMouseOver, onMouseOut, etc.., to make sure they are working properly. Try implementing the same for onChange for an input element.

    See the documentation for actions here.

    Background

    We can change the background of the preview window using the background addon. We don’t have to write any code. Just change it inside the storybook. You can see the gif below.

    Viewport

    We can also test the responsiveness of our components in the storybook. See the gif below to find out about the viewport options.

    Docs

    We can document our components in the storybook using the docs addon. It’s more useful when we are working in a team. They will read the component and understand it directly. It saves a lot of time for the developers.

    In storybooks’ components preview window you can see Docs at the top right to the Canvas tab. It will contain all docs of all the stories of a component. We have to use Button.stories.mdx if we want to document for the component that includes both markdown and component rendering. We just write some extra markdown code inside it along with the component stories.

    We are writing a document for our stories. The code includes markdown and component rendering. It’s all just learning the syntax. You will get it first glance.

    Let’s see the Button.stories.mdx doc code.

    <!--- Button.stories.mdx -->
    
    import {
        Meta,
        Story,
        Preview,
        ArgsTable
    } from '@storybook/addon-docs/blocks';
    
    import { Button } from './Button';
    
    <Meta title="MDX/Button" component={Button} />
    
    # Button Documentation
    
    With `MDX` we can define a story for `Button` right in the middle of our
    Markdown documentation.
    
    <ArgsTable of={Button} />
    
    export const Template = (args) => <Button {...args} />
    
    ## Default Button
    We can write the documentation related to the Default Button
    <Preview>
        <Story name="Default Button" args={{
            text: 'Default Button'
        }}>
        {Template.bind({})}
       </Story>
    </Preview>
    
    ## Large Button
    We are writing sample docs for two stories, you can write rest of them
    <Preview>
        <Story name="Large Button" args={{
            text: "Large Button",
            }}>
            {Template.bind({})}
        </Story>
    </Preview>

    Find out more about the documenting components here.

    You can find out more about add-ons here.

    Conclusion

    Hope you enjoyed the tutorial and learned about the storybook. And use it effectively in your team to make your work productively.

    New to React? Check out these learning resources.

    Happy Coding 🙂