[fa icon="calendar"] Publié le 05 November 2018 par Laure Retru-Chavastel


Creating and maintaining a robust library of components can quickly become a tiring task. On my current project, using Storybook became a great way to make sure our components were reusable and to detect bugs faster. I will describe in this article how we integrated Storybook in our workflow. First, how to install Storybook, then how to efficiently use Knobs and Storyshots add-ons, and finally sharing some tips with you!

So first of all, what is Storybook? Storybook is an isolated environment where you can design and visualize the different states of your components.

Storybook UI

I will use the example of a basic React project, that includes a simple Button component:

import React from 'react';
import styled from 'styled-components';

const StyledButton = styled.button`
  height: ${props => (props.big ? 46 : 36)}px;
  outline: none;
  padding: 0 20px;
  border-radius: 4px;

  &.primary {
    background-color: #1585d8;
    & span {
      color: white;
    }
  }

  &.secondary {
    background-color: #eff3f6;
    & span {
      color: grey;
    }
  }
`;

const Button = props => (
  <StyledButton
    big={props.big}
    className={props.theme}
    onClick={props.onClick}
    style={props.style}>
    {props.label && <span>{props.label}</span>}
  </StyledButton>
);

Button.defaultProps = {
  big: false,
  label: undefined,
  onClick: () => {},
  style: {},
  theme: 'primary',
};

export default Button;

1. Installation of Storybook

Those 2 lines of code will install Storybook in your React project:

cd my-project-directory 
npx -p @storybook/cli sb init

For further instructions, I invite you to read Storybook documentation.

With the command npm run storybook, you will be able to access Storybook interface. Here is an example of a basic story for our component:

import React from 'react';
import { storiesOf } from '@storybook/react';
import Button from '../components/Button';

storiesOf('Button', module)
 .add('with label', () => <Button label='Hello Button' />);

BasicStory-1

Visualization of our basic story in Storybook 

2. Knobs add-on

Goal: Edit your component properties in real-time in Storybook

This add-on is the one that we use the most in our components stories, and since it is not preinstalled with Storybook, I will first show you how to install it:

npm install @storybook/addon-knobs --save-dev

Then, import the add-on in the file addons.js located in “.storybook” folder: 

import '@storybook/addon-knobs/register'; 

You should now see a tab called “Knobs” in your Storybook interface, and start using it in your stories like this:

import React from 'react';
import { storiesOf } from '@storybook/react';
import { withKnobs, text, select, boolean, object } from '@storybook/addon-knobs';
import Button from '../components/Button';

const themes = ['primary', 'secondary'];

storiesOf('Button', module)
 .addDecorator(withKnobs)

 .add('with label', () => <Button label={text('label', 'Hello Button')} />)
 .add('with big', () => <Button label="Big Button" big={boolean('big', true)} />)
 .add('with theme', () => (
   <Button label={text('label', 'Themed button')} theme={select('theme', themes)} />
 ))
 .add('custom style', () => (
   <Button
     label="Custom style"
     style={object('style', {
       backgroundColor: 'pink',
       border: '5px dotted limegreen',
     })}
   />
 ));

You can now easily verify that your properties have the behaviour expected. Moreover, since Storybook is connected to your component, any change you make to it will be observable in Storybook in real time.

knobs_demo_article

Useful tip: Add a playground story

In order to fully test your component, I recommend to create a story where you will use knobs for every property your component has:

storiesOf("Button", module)
 .addDecorator(withKnobs)

 .add('playground', () => (
   <Button
     label={text('label', 'Hello World !')}
     big={boolean('big', true)}
     theme={select('theme', themes)}
     style={object('style', {})}
   />
 ));

In your Storybook, you now have a story that allows you to modify all your properties at once.

Main advantages of Knobs add-on

  • Allow a new developer to quickly see all the properties of your component and how they can be combined in one place.
  • Detect bugs easily by testing edge cases directly in Storybook instead of waiting the reload of your changes in the context of your application.

EdgeCasesBugStorybook

Example of label property edge case: too long text escapes from the button

  • Detect unexpected behaviour by testing multiple combinations of properties.

MultipleCombinationPropsBug
Example of property loading which does not render well with secondary theme

3. Storyshots add-on

Goal: Add automatic Jest snapshot testing to all your stories

No excuse for forgetting to add a snapshot anymore: each time you write a story for a component, a snapshot is automatically added by this add-on. If needed, have a look to this article for an introduction on Jest snapshots.

Installation steps for a React project with Jest configured:

npm install --save-dev @storybook/addon-storyshots
npm install --save-dev react-test-renderer

Then create a file Storyshots.test.js in your stories folder:

import initStoryshots from '@storybook/addon-storyshots';
initStoryshots(); 

You can now use the usual npm test command, and it will create Jest snapshots for your stories:

StoryshotsCreated

Useful tip: create stories to deal with no properties and/or edge cases

In order to have a snapshot for every edge cases, you should consider adding stories that deal with "too large" or empty values. For example, if one of your properties is an array, add a story where this property is an empty array. If one of them is a text, add a story where this property is a very long text.

Also, add a story that renders your component without any property to check if your component properly render:

storiesOf('Button', module)
  .addDecorator(withKnobs)

  .add('with no prop', () => <Button />)

NoPropsStorybook

That way, you will be able to easily check your edge cases in Storybook, and have related snapshots generated by Storyshots add-on. 

Another tip: add multiple values of your properties in the same story

I presented you earlier a story for the “theme” property of our Button component. Now that Storyshots is installed and that you have a story “playground”, a good idea will be to delete the knobs method in the story “with theme” and rather add a Button component for each value of this property in it:

storiesOf('Button', module)
  .addDecorator(withKnobs)

  .add('with theme', () => (
    <React.Fragment>
      <Button label="Button with primary theme" theme="primary" />
      <Button label="Button with secondary theme" theme="secondary" />
    </React.Fragment>
  ))
  .add('playground', () => (
    <Button
      label={text('label', 'Hello World !')}
      big={boolean('big', true)}
      theme={select('theme', themes)}
      style={object('style', {})}
    />
  ));

MultipleValuesPropStorybook

Storyshots will then automatically create snapshots for those two values. Playground story will allow you to select one or the other, whereas story “with theme” will show you the two available values for this property. 

Another tip: use jest-styled-components to include CSS in your snapshots

As you could see in the previous example of story snapshot, style is not clearly shown in snapshots when you use styled components. Instead of the actual style, you only see a hash in className property. In order to make your snapshots more precise, I recommend using jest-styled-components library that allows to show related CSS in snapshots:

yarn add --dev jest-styled-components

Then import it at the beginning of your Storyshots.test.js:

import jest-styled-components

Here is how the previous snapshot now looks like: 

StoryshotsWithCss

Main advantages of Storyshots add-on

  • Snapshots are automatically created for every story you add in Storybook: no need to create them yourself anymore.
  • By displaying multiple components in the same story to show different values of your property, you will be sure to have a snapshot for every state of your component. To keep the nice feature described earlier that allows you to switch between values with the Knobs method “select”, you will still have the story “playground” available!

This being said, be careful to add stories for specific edge cases (like without any props or in data loading state), as you will do by writing a specific snapshot test on your own.

4. Bonus: some more tips

  • Add an extension to your stories files

I recommend using the extension ".stories.js" for all your stories to easily identify them and separate them from classic files of your project. (⚠️do not forget to update your config.js file if needed).

  • Use a dynamic loading of stories

This will allow you to automatically add any new stories file to your Storybook, without worrying about adding it in an index file. Modify your config.js file as below to do so:

import { configure } from '@storybook/react';

const req = require.context('../stories', true, /\.stories\.js$/);

function loadStories() {
 req.keys().forEach(filename => req(filename));
}

configure(loadStories, module);
  • Use a snippet to create a new story files

Creating a new story file is already an easy thing to do, but using a snippet will make you be even more efficient. It should now take only a few minutes! To help you create your own, here is the snippet we use in our project:

"Storybook stories": {
  "prefix": "reactstory",
  "description": "Create a new story for storybook",
  "body": [
    "import React from 'react';",
    "",
    "import { storiesOf } from '@storybook/react';",
    "import { boolean, color, object, select, text, withKnobs } from '@storybook/addon-knobs';",
    "import { action } from '@storybook/addon-actions';",
    "import ${TM_FILENAME/(.*)\\.stories\\..+$/$1/} from \"yourprojectalias/components/${TM_FILENAME/(.*)\\.stories\\..+$/$1/}\"",
    "",
    "",
    "storiesOf('${TM_FILENAME/(.*)\\.stories\\..+$/$1/}', module)",
    ".addDecorator(withKnobs)",
    "",
    ".add('with no props', () => (",
    "<${TM_FILENAME/(.*)\\.stories\\..+$/$1/} />",
    "))",
    "",
    ".add('loading', () => (",
    "<${TM_FILENAME/(.*)\\.stories\\..+$/$1/} isLoading={boolean('isLoading', true)} />",
    "))",
    "",
    ".add('playground', () => (",
    "<${TM_FILENAME/(.*)\\.stories\\..+$/$1/}",
    "label={text('label', 'example')}",
    "onClick={action('onClick')}",
    "isLoading={boolean('isLoading', true)}",
    "/>));"
  ]
}

Conclusion 

You should now be eager to install and use Storybook in your project! Let me summarize why:

  • Storybook is an isolated environment: a perfect place to design and test your components to be sure that they could be reused easily
  • By writing as many stories as you have properties and edge cases, you can visualize every state of your components in one place
  • Knobs add-on allows you to edit your component properties in real time, so you could detect potential bugs before using the component in your application
  • Storyshots add-on allows you to automatically create snapshots for all your stories, so you can now save time and let this add-on do it for you

Liked this article? Interested in building an app with us?

Contact a React Native expert in Paris




Want to rate this article?
Submit Rating

Topics: React, Storybook, React Storybook