Documenting React Components with Storybook - department-of-veterans-affairs/caseflow GitHub Wiki

What is Storybook? Why is it useful?

Storybook is an open source tool for developing UI components in isolation. It allows us to develop, document, and demo our frontend React components separate from their normal place within the Caseflow application.

This means we can show both core UI components (such as buttons, tables, form fields, etc) as well as individual portions of a particular Caseflow page without needing to know the detailed steps to get to it in the application and without the need for a fully populated Redux store, for instance.

How to access Storybook

A live version of Storybook can be accessed from the Caseflow demo site. This will reflect any stories that have been merged into the master branch following the same process as the normal Caseflow application.

One can also run Storybook from a local development environment. First, ensure you have the latest dependencies by running yarn install from the caseflow/client folder. Then you can launch Storybook by running yarn storybook. This will compile the stories, start a local server, and open a browser window to the Caseflow Storybook page. This local process supports hot module reload (HMR) so changes that you make to stories (or other components) will be instantly reflected.

Multiple ways to write a story

Storybook supports writing stories in multiple formats:

  • Component Story Format (CSF)
  • MDX, which mixes Markdown and JSX stories
  • Legacy "storiesOf" format (many examples on the web use this, but the newer formats are now preferred)

The CSF format is written in pure JavaScript, and is pretty straightforward. The MDX format unlocks much richer and more customizable options for writing documentation. It is even possible to use both for the same components, writing basic stories in CSF and more complex documentation using MDX.

The easiest way to use this mixed approach is to create both a .js file (where the stories will be written), and a .mdx file (from where the stories will be referenced). When doing this, the key is to not have the export default in the JS file, and to instead import and reference the stories like this in the MDX:

import * as ButtonStories from './Button.stories.js';

<Preview>
  <Story story={ButtonStories.Basic} />
</Preview>

Where to write the stories

For Caseflow, we have set up Storybook to look for stories in two places:

  • inline alongside the component that is being documented (most cases)
  • within the client/stories folder (certain special cases)

In either case, stories should generally be in the form of ComponentName.stories.js (for CSF) or ComponentName.stories.mdx (for MDX). It will ignore files without ".stories" in the filename.

While most stories should be written in the same folder as the component being documented, we use the client/stories folder for a few special cases where that would not make sense:

  • documenting components from outside the main repo (such as those from caseflow-frontend-toolkit)
  • pure documentation or stories that don't line up with a particular existing component (things like our introduction text, typography, colors, etc)

Addons

Addons allow Storybook's functionality to be extended and customized in various ways. We are currently using these addons:

Documenting components

Storybook automates much of the process of documenting a component. It does so by analyzing various hints present in the component's code, which makes following best practices all the more important as we build components.

The props table (<Props of={ComponentName} /> in MDX format) is populated by a combination of the propTypes and defaultProps properties of a component. If those are incomplete, so too will be our documentation.

For MDX stories, the author has full control over the descriptions of a component by writing in Markdown. In CSF format, Storybook will generate various bits of documentation from react-docgen comments. This means that you can automatically generate docs for a component by simply adding a few comments to your code:

import React from 'react';
import PropTypes from 'prop-types';

/**
 * MyComponent doesn't do much, but this demonstrates the description!
 */
export const MyComponent = ({title}) => <h1>{title}</h1>;
MyComponent.propTypes = {
  /**
   * Description of the "title" prop. This will show up in the props table.
   */
  title: PropTypes.string
}
MyComponent.defaultProps = {
  title: 'Default title that will also be indicated in props table'
}

One can also specify general or story-specific descriptions by specifying the docs.description parameter in your CSF story file:

import { Button } from './Button';

export default {
  title: 'Button'
  parameters: { docs: { description: { component: 'some component **markdown**' }}}
}

export const Basic = () => <Button />
Basic.parameters = { docs: { description: { story: 'some story **markdown**' }}}

Components that require providers

What if the component one is documenting relies on an external context provider, such as react-redux or react-router?

First, determine if such dependencies are really necessary, or if you could instead focus your story on just the presentational component(s) involved. This might be a good impetus to refactor and/or add an export of the component without the connect HoC of redux for instance.

If you decide that your story requires these external context providers, the best way to do it is by utilizing Storybook decorators. These allow you to wrap your story with whatever is necessary without needing to add it into each individual story.

Known issues

  • When writing MDX stories, be sure to not have empty lines in your actual JSX component code — it can break the MDX parser. Empty lines in the markdown portions are fine, however.
⚠️ **GitHub.com Fallback** ⚠️