Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Practical Guide to Storybook-Driven Development

DZone's Guide to

Practical Guide to Storybook-Driven Development

A tutorial on how to use the Storybook tool as a means of templating and driving forward your development efforts. Read on to get started!

· Web Dev Zone ·
Free Resource

Deploying code to production can be filled with uncertainty. Reduce the risks, and deploy earlier and more often. Download this free guide to learn more. Brought to you in partnership with Rollbar.

So a few weeks ago, while having a discussion with my team about the approach to developing self-contained React components, I came across this awesome tool: Storybook. To my surprise, I had seen this before, in the example of react-dates.

Storybook is basically a playground for developing your components and their behavior. It also serves as documentation for your component library. You can showcase your components and their different alterations that are linked to props. After playing a bit with Storybook and watching this talk on Storybook Driven Development, I decided to make a practical guide on how to get you started.

So, as you all know, React is a library for making User Interfaces. In the last years, due to libraries like styled-components, the interest in making self-contained components (styling and JSX in one JavaScript file) has risen in the React community. React Native started this movement and I always was a fan of it. In this regard, Storybook helps us out with the encapsulated design and development of our components.

So What Do I Need to Get Started?

So we are going to start with a clean sheet:

npx create-react-app storybook-driven-development
npm i -g @storybook/cli

Navigate to your project and add the following dependencies:

npm install --save styled-components

Then, after that, to add Storybook to your project you just have to run:

getstorybook

This will automatically add Storybook to your project and add the required dependencies. To run the Storybook instance of your project just run:

npm run storybook

Now you can navigate to http://localhost:9009/ and see your playground with Storybook. Easy, eh?

Writing Your First Story

So the whole point of Storybook Driven Development is that you actually “sketch” your components first in code before actually coding the implementation. This can be a great start for the development of your components because you can think of the different states you want your component to have before you make the implementation. For example, if you want to make a Button component you can make a default, primary, info, danger, and warning component (Hello Bootstrap!). But how does that look?

First of all, you will see a stories folder with an index.js file. Here you will find some example code on how to write stories. For our button examples, we can begin with this:

import React from 'react';
import { storiesOf } from '@storybook/react';
import {Button, H1} from  '../components/shared';
import { action } from '@storybook/addon-actions';
import { withInfo } from '@storybook/addon-info';
import { text, boolean, number } from '@storybook/addon-knobs/react';


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


stories
  .add('Primary', () => <Button type="primary" size="lg">Primary button</Button>)
  .add('Default', () =><Button type="default" size="lg" >Default</Button>)
  .add('Info', () => <Button type="info" size="lg">Info button</Button>)
  .add('Warning', () => <Button type="warning" size="sm">Warning</Button>)
  .add('Danger', () => <Button type="danger" size="xs">!</Button>)
  .add('Testing default props', () => <Button>Default props</Button>)

Pretty simple right? If we let Storybook compile this, it will give you an error because you don’t have a Button component. So let’s make one.

import React from 'react';

export default (props) => {
  return(
    <button>
      {props.text}
    </button>
  )
}

After our first version of the button component is made we can already see our buttons in storybook.

After this, we can start actually coding the state in our components and testing them in storybook. For this, we will use styled-components. We can start by adding the styled-components imports and making our button wrapper. After that, we can start playing with the props that are given to make our different states. Our  Button  component will look like this:
import React from 'react'
import styled from "styled-components";
import PropTypes from 'prop-types';


const ButtonWrapper = styled.button`
  background-color: ${props => {
    switch (props.type) {
      case 'primary':
        return 'green';
      case 'default':
        return 'blue';
      case 'info':
        return 'yellow';
      case 'warning':
        return 'orange';
      case 'danger':
        return 'red';
      default:
        return 'blue';
    }
  }};
  font-weight:600;
  font-size:${(props) => props.theme.fontSize};
  padding:0.5em;
  color:white;
  border-radius:0.5em;
  width:${props => {
    switch (props.size) {
      case 'xs':
        return '4em';
      case 'sm':
        return '8em';
      case 'md':
        return '12em';
      case 'lg':
        return '16em';
      default:
        return '8em';
    }
  }}
`;

const Button = (props) => (<ButtonWrapper {...props}>{props.children} </ButtonWrapper>)

Button.defaultProps = {
  type: 'primary'
}

Button.propTypes = {
  //** Indicates which kind of button it is. */
  type: PropTypes.string,
}

export default Button;

The implementation follows my storybook blueprint, which means I will write my component according to my story. In our stories, we have two props: type and size. We also have the children of the component which in this case will render our button text. On top of our component are our styles. Using styled-components conditionally set some specific styles. For the background color and the width of the button, we used the type and size. We can also define our default props in case you forget to set your props. This can be done with prop types like this:

const Button = (props) => (<ButtonWrapper {...props}>{props.children} </ButtonWrapper>)

Button.defaultProps = {
  type: 'primary'
}

Button.propTypes = {
  //** Indicates which kind of button it is. */
  type: PropTypes.string,
}

export default Button;

After our implementation our buttons will have some life.

Addons

So what's also interesting about Storybook is the amount of add-ons that will make your development easier and more predictable. I will highlight a few of these. To get started with these add-ons you have to run:

npm i --save-dev @storybook/addon-actions @storybook/addon-info @storybook/addon-viewport storybook-addon-styled-component-theme @storybook/addon-knobs

After this we have to do some tweaks in our addons.js and config.js files (cd ./storybook )

addons.js:

import '@storybook/addon-actions/register';
import '@storybook/addon-viewport/register';
import '@storybook/addon-knobs/register'
import 'storybook-addon-styled-component-theme/dist/register';

config.js

import { configure, addDecorator } from '@storybook/react';
import { withKnobs } from '@storybook/addon-knobs/react';

addDecorator(withKnobs);

function loadStories() {
  require('../src/stories');
}
configure(loadStories, module);

Actions

Using actions in Storybook is pretty simple. You just import the action function and use it in your handlers:

stories
  .add('Primary', () => <Button type="primary" onClick={action('Clicked!')} size="lg">Primary button</Button>)

Then when clicking on your button you will get an entry in your action logger.

withInfo

The withInfo addon provides you with extra information about your component. You can set a description for your component and see the original JSX as well as the props the component supports.

stories
  .add('Default', withInfo('This is an informational paragraph you can use to describe your component')(
    () =><Button type="default" size="lg" onClick={action('onClick')}>Default</Button>
  ))

And this is how it looks in Storybook:

Knobs

The Knobs addon lets you change props dynamically from the storybook UI. Let’s say for example we want to add a disabled function to our button and we want to also change the color if it’s disabled. In the stories it will look like this:

stories.add('Warning', () => 
  <Button disabled={boolean('Disabled', false)} type="warning" size="sm">Warning</Button>)

You use utility functions from the knobs add-on so it knows which type of knobs it will render. In this case, it’s a boolean and it will render a checkbox. We need to then add the change for the disabled color:

background-color: ${props => {
    if(props.disabled){
      return 'grey';
    }
    switch (props.type) {
      case 'primary':
        return 'green';
      case 'default':
        return 'blue';
      case 'info':
        return 'yellow';
      case 'warning':
        return 'orange';
      case 'danger':
        return 'red';
      default:
        return 'blue';
    }
  }};

And in action:

Viewport

Viewport is also a very useful addon because you can test the responsiveness of your components. It provides device viewport simulation just like the one in Chrome.

Bonus: Themes With styled-components

One big feature that styled-components provides is it’s ThemeProvider. The ThemeProvider is a wrapper component for your app that will a theme prop to your components. This will allow you to change your styling based on the theme you have defined. In storybook you can change the themes from the UI.

So let’s change our code so it supports Theming. We can define our themes in a separate JavaScript file:

export const coolblue = {
  name: "Coolblue theme",
  fontSize: '0.9rem',
  h1Size: '2.8rem',
  h1Color:'#285dab'
};


export const airbnb = {
  name: "Airbnb theme",
  fontSize: '0.9rem',
  h1Size: '2.8rem',
  h1Color:'#FF5A5F'
};

export const amazon = {
  name: "Amazon theme",
  fontSize: '0.9rem',
  h1Size: '2.8rem',
  h1Color:'black'
}

Then we can wrap our App.js with the ThemeProvider and adjust our storybook configuration so it supports themes.

import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';
import { amazon, coolblue, airbnb } from '../src/components/themes/themes';
import Button from './components/shared/Button';
import { ThemeProvider } from "styled-components";

class App extends Component {
  render() {
    return (
      <ThemeProvider theme={airbnb}>
        <div className="App">
          <Button type="default" size="lg">Default</Button>
        </div>
      </ThemeProvider>
    );
  }
}

export default App;

config.js :

import { configure, addDecorator } from '@storybook/react';
import {amazon, airbnb, coolblue} from '../src/components/themes/themes';
import { withKnobs } from '@storybook/addon-knobs/react';
import {withThemesProvider} from 'storybook-addon-styled-component-theme';

const themes = [amazon, airbnb, coolblue];
addDecorator(withThemesProvider(themes));
addDecorator(withKnobs);

function loadStories() {
  require('../src/stories');
}
configure(loadStories, module);

After this, we can make a heading story and it’s implementation to show the theme’s functionality.

story:

const h1Stories = storiesOf('h1', module);
h1Stories
  .add('H1 element', () => <H1>This is a very simple Heading component</H1>)

implementation:

import React from 'react'
import styled from "styled-components";
import PropTypes from 'prop-types';


const H1wrapper = styled.h1`
  color:${(props) => props.theme.h1Color};
  font-size:${(props) => props.theme.h1Size};
`;

const H1 = (props) => (<H1wrapper {...props}>{props.children} </H1wrapper>)

export default H1;

And see it in action:

Conclusion

There are many benefits to trying out Storybook and SDD. It will streamline your component design and creation process, it will serve as documentation for onboarding new developers in your team and also help you with applying encapsulated component design. The added bonus of testing your components is also very nice and there are many community add-ons that will make your storybook experience more productive. It can also be used with Angular and Vue.

Thank you for reading! If you like articles like this let me know in the comment section!

PS: All the code for this article can be found on this repo.

Deploying code to production can be filled with uncertainty. Reduce the risks, and deploy earlier and more often. Download this free guide to learn more. Brought to you in partnership with Rollbar.

Topics:
react ,storybook ,web dev ,front-end development ,tutorial

Published at DZone with permission of

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}