fbredius/storybook

View on GitHub
docs/writing-stories/stories-for-multiple-components.md

Summary

Maintainability
Test Coverage
---
title: 'Stories for multiple components'
---

It's useful to write stories that [render two or more components](../writing-stories/introduction.md#stories-for-two-or-more-components) at once if those components are designed to work together. For example, `ButtonGroups`, `Lists`, and `Page` components. Here's an example with `List` and `ListItem` components:

<!-- prettier-ignore-start -->

<CodeSnippets
  paths={[
    'react/list-story-with-subcomponents.js.mdx',
    'react/list-story-with-subcomponents.ts.mdx',
    'angular/list-story-with-subcomponents.ts.mdx',
    'vue/list-story-with-sub-components.2.js.mdx',
    'vue/list-story-with-sub-components.3.js.mdx',
  ]}
/>

<!-- prettier-ignore-end -->

Note that by adding `subcomponents` to the default export, we get an extra pane on the ArgsTable, listing the props of `ListItem`:

![Storybook story with subcomponent argstable](./argstable-subcomponents.png)

The downside of the approach used above, where each story creates its own combination of components, is that it does not take advantage of Storybook [Args](../writing-stories/args.md) meaning:

1. You cannot change the stories via the controls panel
2. There is no [args reuse](../writing-stories/introduction.md#using-args) possible, which makes the stories harder to maintain.

Let's talk about some techniques you can use to mitigate the above, which are especially useful in more complicated situations.

## Reusing subcomponent stories

The simplest change we can make to the above is to reuse the stories of the `ListItem` in the `List`:

<!-- prettier-ignore-start -->

<CodeSnippets
  paths={[
    'react/list-story-unchecked.js.mdx',
    'react/list-story-unchecked.ts.mdx',
    'vue/list-story-unchecked.2.js.mdx',
    'vue/list-story-unchecked.3.js.mdx',
    'angular/list-story-unchecked.ts.mdx',
    'web-components/list-story-unchecked.js.mdx',
    'web-components/list-story-unchecked.ts.mdx',
  ]}
/>

<!-- prettier-ignore-end -->

By rendering the `Unchecked` story with its args, we are able to reuse the input data from the `ListItem` stories in the `List`.

However, we still aren’t using args to control the `ListItem` stories, which means we cannot change them with controls and we cannot reuse them in other, more complex component stories.

## Using children as an arg

One way we improve that situation is by pulling the rendered subcomponent out into a `children` arg:

<!-- prettier-ignore-start -->

<CodeSnippets
  paths={[
    'react/list-story-with-unchecked-children.js.mdx',
    'react/list-story-with-unchecked-children.ts.mdx',
  ]}
/>

<!-- prettier-ignore-end -->

Now that `children` is an arg, we can potentially reuse it in another story.

However, there are some caveats when using this approach that you should be aware of.

The `children` `args` as any other arg needs to be JSON serializable. It means that you should:

- Avoid using empty values
- Use caution with components that include third party libraries

As they could lead into errors with your Storybook.

<div class="aside">

We're currently working on improving the overall experience for the children arg and allow you to edit children arg in a control and allow you to use other types of components in the near future. But for now you need to factor in this caveat when you're implementing your stories.

</div>

## Creating a Template Component

Another option that is more “data”-based is to create a special “story-generating” template component:

<!-- prettier-ignore-start -->

<CodeSnippets
  paths={[
    'react/list-story-template.js.mdx',
    'react/list-story-template.ts.mdx',
    'vue/list-story-template.2.js.mdx',
    'vue/list-story-template.3.js.mdx',
    'angular/list-story-template.ts.mdx',
  ]}
/>

<!-- prettier-ignore-end -->

This approach is a little more complex to setup, but it means you can more easily reuse the `args` to each story in a composite component. It also means that you can alter the args to the component with the Controls addon:

<video autoPlay muted playsInline loop>
  <source
    src="template-component-with-controls-optimized.mp4"
    type="video/mp4"
  />
</video>