Tutorial

Using Storybook with React & Redux

Published on January 8, 2019
Default avatar

By Chris Dolphin

Using Storybook with React & Redux

While we believe that this content benefits our community, we have not yet thoroughly reviewed it. If you have any suggestions for improvements, please let us know by clicking the “report an issue“ button at the bottom of the tutorial.

Storybook allows you to develop UI components in isolation, which can improve component reuse, testability, and development speed.

Along with these developer experience (DX) benefits, Storybook works nicely with traditional component development and doesn’t require changes to core functionality to display, test, and document your component library.

It can also provide further extension through “addons” and “decorators”. With Addons, the possibilities to mesh Storybook with your development flow are unlocked. Decorators then add the customization that your components need to run smoothly.

Storybook with React

Getting React up and running with Storybook is relatively simple. The full setup instructions are available in the official docs. This process will provide a .storybook/ directory and a config.js file. Once those pieces are in place, creating stories for React components is straightforward.

//in Button.story.js

import React from 'react'
import { storiesOf } from '@storybook/react'

import Button from './Button'

storiesOf('Button', module)
  .add('default', () => (
    <Button>Submit</Button>
  ))
//in .storybook/config.js

import { configure } from '@storybook/react'

configure(function() {
  require('../src/components/Button.story')
}, module)
$ npm run storybook

Running the command to launch Storybook will show the new story in the Stories Panel.

Provider component

Because Storybook presents our components in isolation, there can be problems recreating some of the functionality that our App might provide at a high-level, commonly found in Root.js or App.js. In Redux this would be a Provider, but this also applies to the Router from react-router, and any Provider from libraries like react-i18next or ThemeProviders from libraries like material-ui.

To hook this up without modifying component code we’ll use a helper provided by Storybook to create Decorators. Decorators allow components to be wrapped in common code, avoiding redundancies and inconsistency.

Before diving into decorators, we’re going to create a Provider component from all our high-level Providers. This could be as simple as exporting Redux Provider, but it can also include the Router, internationalization setup, etc.

//in Provider.js

import React from 'react'
import { Router } from 'react-router'
import { Provider } from 'react-redux'

const ProviderWrapper = ({ children, store }) => (
  <Provider store={store}>
    <Router>
      { children }
    </Router>
  </Provider>
)

export default ProviderWrapper

With the Provider separated out from the Root, it can easily be imported into any component story.

Along with importing the Provider we need to import and pass along an instance of our Redux store.

//in Button.story.js

...

import { storiesOf } from '@storybook/react'

import Provider from '../Provider.js'
import configureStore from '../configuedStore.js'

const store = configureStore()

...

storiesOf('Button', module)

This provides the story with easy access to store.dispatch so Redux actions can be called to simulate state changes in our component.

Decorators

Storybook Decorators allow stories to be wrapped in common code that the component might need to perform correctly. A common example is a wrapper to center a component in the page, but we’ll use a decorator to inject the Provider component into our stories, much like a HOC.

A simple implementation would be as follows.

//in Button.story.js

// ...

import Provider from '../Provider.js'
import configureStore from '../configuedStore.js'

const store = configureStore()

const withProvider = (story) => (
  <Provider store={store}>
    { story() }
  </Provider>
)

storiesOf('Button', module)
  .addDecorator(withProvider)
  .add('default', () => (
    <Button>Submit</Button>
  ))

This withProvider decorator can be turned into a utility function for use in other stories. For convenience, these decorators are normally kept in a new .storybook/decorators.js file so they can be imported throughout the codebase.

Global Decorators

As an additional help, Storybook provides a way to easily apply decorators to all stories globally. This can be accomplished by calling addDecorator in .storybook/config.js

//in .storybook/config.js

import { configure, addDecorator } from '@storybook/react'
import { withProvider } from './decorators'

// ...

addDecorator(withProvider)

DevTools

Redux DevTools

Redux DevTools is a “power-up” when developing with Redux, providing visuals for state changes, action replays, and more.

After installing the browser extension, there is some code configuration needed in the store.

//in configureStore.js

import { createStore, applyMiddleware } from 'redux'
import { composeWithDevTools } from 'redux-devtools-extension'

const store = createStore(reducer, composeWithDevTools(
  applyMiddleware(...middleware),
))

In the past Redux DevTools has had trouble working with Storybook as it isolates all components into an iframe. This prevented the extension from listening to the Redux actions within our stories.

Even though this isn’t as much of an issue anymore, there are a few Addons that were created to help.

Addons

Storybook Native Addons add further development functionality to Storybook. These can be extensions written by the community or custom to your development flow. As part of their configuration, addons are registered in a new .storybook/addons.js file.

storybook-addon-redux-listener

Before the issue was fixed more directly, this Redux DevTools listener addon reestablished the components’ connection to the browser extension. It also adds a simple panel to Storybook itself.

Configuration looks something like this:

//in .storybook/addons.js

import 'storybook-addon-redux-listener/register'
//in configureStore.js

import createStorybookListener from 'storybook-addon-redux-listener'

...

const middlewares = []

// OPTIONAL: attach when Storybook is active
if (process.env.NODE_ENV === 'storybook') {
  const reduxListener = createStorybookListener()
  middlewares.push(reduxListener)
}

const createStoreWithMiddleware = (reducers) => {
  return createStore(reducers, applyMiddleware(...middlewares))
}

const configureStore = () => createStoreWithMiddleware(reducers)

export default configureStore

addon-redux

This addon adds multiple custom panels that recreate some of the functionality from the DevTools.

//in .storybook/addons.js

import addons from '@storybook/addons'
import registerRedux from 'addon-redux/register'

registerRedux(addons)
//in configureStore.js

import { createStore, compose } from 'redux'
import reducer from './your/reducer'
import withReduxEnhancer from 'addon-redux/enhancer'

const configureStore = () => createStore(reducer, withReduxEnhancer)

export default configureStore

See Also

Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.

Learn more about us


About the authors
Default avatar
Chris Dolphin

author

Still looking for an answer?

Ask a questionSearch for more help

Was this helpful?
 
Leave a comment


This textbox defaults to using Markdown to format your answer.

You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!

Try DigitalOcean for free

Click below to sign up and get $200 of credit to try our products over 60 days!

Sign up

Join the Tech Talk
Success! Thank you! Please check your email for further details.

Please complete your information!

Get our biweekly newsletter

Sign up for Infrastructure as a Newsletter.

Hollie's Hub for Good

Working on improving health and education, reducing inequality, and spurring economic growth? We'd like to help.

Become a contributor

Get paid to write technical tutorials and select a tech-focused charity to receive a matching donation.

Welcome to the developer cloud

DigitalOcean makes it simple to launch in the cloud and scale up as you grow — whether you're running one virtual machine or ten thousand.

Learn more
DigitalOcean Cloud Control Panel