adjoe Engineers’ Blog
 /  Frontend  /  Writing Stories with Storybook to Visualize React Dashboard Components
decorative purple image with colorful elements
Frontend

Writing Stories with Storybook to Visualize React Dashboard Components

In my last article, I explained how your codebase should look in order to seamlessly integrate and use Storybook to document and interact with your components. I also took you through the process of how we integrated Storybook in our new dashboard project, explained how to solve common integration problems, and demonstrated how to automatically upload your documentation to Chromatic, Storybook’s own hosting service.

In this article, I’ll explain the process of writing stories with Storybook for both simple components, like buttons, and more complex state-based components, like filters and dropdowns.

What Is a Story?

When we talk about writing stories with Storybook, a story describes and visualizes a component’s specific state. A component can have multiple stories, based on which states the component can support. Different stories are normally used to show how the component transforms based on which properties are given to it. 

Screenshot of a button story with four substories in Storybook in article about writing stories with Storybook

A story itself has two views: Canvas and Docs.

The Canvas view shows the single story with controls below it. With these controls, you can set all of the component’s properties. This view can also be used to interact with the component. You can change the size of the page to see media queries in action, zoom in and out, switch to dark mode, show the grid, and more.

 The Canvas view also supports various add-ons you can install for your Storybook. You can find a ton of different add-ons here.

Screenshot of the Canvas page of a Button story in Storybook

The Docs view is the documentation page of the component.

Here, you can view the written description of the component. You’ll see a list of all supported properties, as well as prop comments set in the code to explain these. 

The Docs view will show all stories on one page, and you can also view code examples for integration.

Screenshot of the Docs page of a button story in Storybook
Screenshot of the substories in the Docs page of a button story in Storybook
Screenshot of the code example of a button story in Storybook

How Do I Write a Story for a Simple Component?

Storybook provides you with a clear structure of how a story should look like.

The story exports a default object typed as a ComponentMeta, which includes the field’s title and component. The title is responsible for the placement of the story in your Storybook. A title string “Common/Button” will create the component story under the folder “Common” entitled “Button.” In the component field, you pass the actual component to render as a React ComponentType.


Then you can create a template, which is the actual component-rendering function. It will return JSX, which will be used to render the component in the story, passing the properties as arguments. This template can return anything you want and is also where you’ll add the necessary logic or state for more complex components later on. For example, you could wrap your button component in a box here and give it a different background, if you want.


Lastly, the actual stories will be created by binding your template to ComponentStory constants.With this, you can create any number of stories for your component and change the arguments of the template according to your needs.

Let’s use a simple component as an example – a simple wrapper of the MUI button.

interface IButtonProps {
 variant: 'text' | 'outlined';
 onClick: () => void;
 children: React.ReactNode;
}
export const MyButton: FC<IButtonProps> = ({ variant, onClick, children }) => (
 <Button variant={variant} onClick={onClick}>
   {children}
 </Button>
);

Out of this component, you can easily create two stories that show different variants.

First, create a new file next to the component and name it “Button.stories.tsx.” Then, create your default export.

export default {
 title: 'Common/Button',
 component: MyButton,
} as ComponentMeta<typeof MyButton>;

In TypeScript, you have to make sure that you type the object correctly and add the typeof component as a generic.

Next, add the template. In this case, it renders the button with a simple text as the child and passes the arguments to it with prop spreading.

const Template: ComponentStory<typeof MyButton> = args => (
 <MyButton {...args}>Button</MyButton>
);

And then you can define your stories with bindings by setting the variant in the args to what you need. The onClick definition is in this case not necessary – you can just assign a function that does nothing, or you could make a console log.

export const DemoOutlined = Template.bind({});
DemoPrimary.args = {
 onClick: () => {},
 variant: 'outlined',
};


export const DemoText = Template.bind({});
DemoSecondary.args = {
 onClick: () => {},
 variant: 'text',
};

How Do I Write Stories for Complex Components?

While simple components, like buttons, do not need any logic to visualize them with stories, more complex components – for example, a select component – will need both a state and state change logic in order to function correctly in the story. In this case, you will need to expand the template with values, state, and change listeners.

Let’s take this simplified select component as an example.

export interface IMySelectProps {
 label?: string;
 values: { name: string; value: string }[];
 selectedValue: string;
 onChange: (event: SelectChangeEvent<string>) => void;
}


export const MySelect: FC<IMySelectProps> = ({
 label,
 values,
 selectedValue,
 onChange,
}) => {
 return (
   <Select value={selectedValue} label={label} onChange={onChange}>
     {values.map(value => (
       <MenuItem value={value.value} key={value.name}>
         {value.name}
       </MenuItem>
     ))}
   </Select>
 );
};

This component doesn’t function on its own; it requires logic on its parent component in order for the user to see and select options. 

Let’s now create a story for it.

First, create a new file called “MySelect.stories.tsx” next to the Select Component file. Then, like in the button story, create the default export object.

export default {
 title: 'Common/Select',
 component: MySelect,
 argTypes: { onChange: { action: 'onChange callback was called with' } },
 args: {
   label: 'Fruit Select',
   values: [
     { name: 'Banana', value: 'banana' },
     { name: 'Lemon', value: 'lemon' },
     { name: 'Orange', value: 'orange' },
   ],
 },
} as ComponentMeta<typeof MySelect>;

You can add two new fields next to the title and component fields used previously.

argTypes lets you set argument options for your story manually. Here, you can add descriptions for the arguments and, like in this case, add an action to it. The action will automatically trigger in the action section of the canvas, and will output the defined string together with the function’s passed parameters.
In args, you can set default arguments that do not change per story. In this case, add the label and values, so it will be set for all of this component’s stories.

Next up, create the story template.

const Template: ComponentStory<typeof MySelect> = args => {
 const { onChange, values } = args;
 const [value, setValue] = useState('banana');


 const onSelectChange = (event: SelectChangeEvent<string>) => {
   onChange(event);
   setValue(event.target.value);
 };
 return (
   <MySelect selectedValue={value} onChange={onSelectChange} values={values} />
 );
};

First, First, you need onChange and values, which you get from args by destructuring.

Create a state, which will save your current selected value as a string, with a default value. 

In order to trigger the action correctly, you need to call the onChange function of the args. Then you can set the state to the selected value from the component.
Instead of spreading the args, assign the state value, the custom change function, and the values from the default args.

Finally, create a simple demo to show the story in Storybook.

export const Demo = Template.bind({});

The result is a fully functioning select dropdown component you can try out in your Storybook.

Screenshot of the dropdown component in Storybook

What’s Next?

You should now have the tools and knowledge to create your own visual stories for your components – regardless of whether they function without external logic or whether they need additional logic in the parent.

After discussing the process of writing stories with Storybook, read my third and final article. I’ll be showing you how to utilize Storybook’s features of documentation to document your components, as well as give you a brief overview of Chromatic’s visual-regression testing tool.