Stories for multiple components
It's useful to write stories that render two or more components at once if those components are designed to work together. For example, ButtonGroups
, Lists
, and Page
components.
Reusing subcomponent stories
The simplest approach we can take is to reuse the stories of the ListItem
in the List
:
import type { Meta, StoryObj } from '@storybook/react';
import { List } from './List';
//๐ Instead of importing ListItem, we import the stories
import { Unchecked } from './ListItem.stories';
export const meta: Meta<typeof List> = {
/* ๐ The title prop is optional.
* See https://storybook.js.org/docs/7/configure#configure-story-loading
* to learn how to generate automatic titles
*/
title: 'List',
component: List,
};
export default meta;
type Story = StoryObj<typeof List>;
export const OneItem: Story = {
render: (args) => (
<List {...args}>
<Unchecked {...Unchecked.args} />
</List>
),
};
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:
import type { Meta, StoryObj } from '@storybook/react';
import { List } from './List';
//๐ Instead of importing ListItem, we import the stories
import { Unchecked } from './ListItem.stories';
const meta: Meta<typeof List> = {
/* ๐ The title prop is optional.
* See https://storybook.js.org/docs/7/configure#configure-story-loading
* to learn how to generate automatic titles
*/
title: 'List',
component: List,
};
export default meta;
type Story = StoryObj<typeof List>;
export const OneItem: Story = {
args: {
children: <Unchecked {...Unchecked.args} />,
},
};
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.
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.
Creating a Template Component
Another option that is more โdataโ-based is to create a special โstory-generatingโ template component:
import type { Meta, StoryObj } from '@storybook/react';
import { List } from './List';
import { ListItem } from './ListItem';
//๐ Imports a specific story from ListItem stories
import { Unchecked } from './ListItem.stories';
const meta: Meta<typeof List> = {
/* ๐ The title prop is optional.
* Seehttps://storybook.js.org/docs/7/configure#configure-story-loading
* to learn how to generate automatic titles
*/
title: 'List',
component: List,
};
export default meta;
type Story = StoryObj<typeof List>;
//๐ The ListTemplate construct will be spread to the existing stories.
const ListTemplate: Story = {
render: ({ items, ...args }) => {
return (
<List>
{items.map((item) => (
<ListItem {...item} />
))}
</List>
);
},
};
export const Empty = {
...ListTemplate,
args: {
items: [],
},
};
export const OneItem = {
...ListTemplate,
args: {
items: [{ ...Unchecked.args }],
},
};
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.