Increase your productivity with Storybook's new Component Story Format

ryanlanciaux

Ryan Lanciaux

Posted on October 24, 2019

Increase your productivity with Storybook's new Component Story Format

Storybook is an incredible tool for building component based frontend applications. It helps you develop the parts of your application in isolation and apply some powerful plugins to ensure quality and consistency. With a recent release of Storybook, there is a new way that we can define our stories that can help us eliminate duplication in other areas of our codebase.

Component Story Format

Traditionally, Storybook stories look like the following code block:

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

import Card from './Card';

storiesOf('Card', module).add('default', () => {
  return <Card>Something</Card>
});
Enter fullscreen mode Exit fullscreen mode

These work well and this traditional format is not going away, however, there are some extra benefits we get by using the new component story format.

The new component story format looks like this:

export default { title: "activityFeed/ActivityFeedItem" };

export const standard = () => (
  <ActivityFeedItem
    name="Bill Murray"
    conferenceName="Some Conference"
    imageUrl="https://www.fillmurray.com/128/128"
  />
)
Enter fullscreen mode Exit fullscreen mode

You may notice that the only Storybook specific item is the default export. The default export is a JavaScript object that takes a title that can be either the title of the story or the path to the story (this example is using a path) and some additional options.

The story definition is now a standard arrow function.

Benefits

One of the most immediate benefits that I've found when using the Component Story Format is testing. My tests can now reuse the stories.

I've traditionally had code in my tests that was very similar to the code in my stories for wiring up components (notice that the use of the ActivityFeedItem in this test is very similar to the story above):

import React from 'react';
import { render, getByText } from '@testing-library/react';
import ActivityFeed from './ActivityFeed';

it('has Bill Murray', () => {
  const { container } = render(
    <ActivityFeedItem
      name='Bill Murray'
      conferenceName='Some Conference'
      imageUrl='https://www.fillmurray.com/128/128'
    />
  );

  expect(getByText(container, 'Bill Murray is speaking at')).toBeDefined();
})
Enter fullscreen mode Exit fullscreen mode

Using the new format, we can leverage the stories we have already created by importing them into our tests:

import React from 'react';
import { render, getByText } from '@testing-library/react';

// import our component from storybook 
// instead of re-wiring a new component for the test
import { standard } from './ActivityFeed.stories';

it('has Bill Murray', () => {
  const { container } = render(standard());

  expect(getByText(container, 'Bill Murray is speaking at')).toBeDefined();
})
Enter fullscreen mode Exit fullscreen mode

This is especially helpful when you have a component that has multiple states. You can make a story that represents each of the various states and import these stories directly in your tests.

(Very contrived code - not real world scenario BUT should help show the concept):

// ActivityFeed.stories.js

export default { title: 'activityFeed/ActivityFeedItem' };

export const withBillMurray = () => (
  <ActivityFeedItem
    name='Bill Murray'
    conferenceName='Some Conference'
    imageUrl='https://www.fillmurray.com/128/128'
  />
)

export const withNicolasCage = () => (
  <ActivityFeedItem
    name='Nicolas Cage'
    conferenceName='Some Conference'
    imageUrl='https://www.placecage.com/128/128'
  />
)

// ActivityFeed.test.js
import { render, getByText } from '@testing-library/react';
import {  withBillMurray, withNicolasCage } from './ActivityFeed.stories';

it('has Bill Murray', () => {
  const { container } = render(withBillMurray());

  expect(getByText(container, 'Bill Murray is speaking at')).toBeDefined();
})

it('has Nicolas Cage', () => {
  const { container } = render(withNicolasCage());

  expect(getByText(container, 'Nicolas Cage is speaking at')).toBeDefined();
})
Enter fullscreen mode Exit fullscreen mode

This technique also works with tools such as Cypress

I'd love to know your thoughts or of any other way that you're achieving better productivity in front-end development with similar strategies.

💖 💪 🙅 🚩
ryanlanciaux
Ryan Lanciaux

Posted on October 24, 2019

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related