An introduction to Storybook
How to get started with a tool which can massively improve your UI development.
July 09, 2020
Storybook
I like Storybook. I think it’s great. Really.
Honestly, It’s one of those tools which for me I wish I discovered and took advantage of sooner. I started using it about a year ago, and seem to try and use it for whatever project I work on. The only two scenarios where I’d say I couldn’t use Storybook for one reason or another - Is Salesforce Lightning Component development, and interestingly - Gatsby. As I say writing a blog post on a Gatsby site… Maybe it’s because I use Windows or something 🤷♂️…
But - Storybook can run on a lot of frameworks - Including React, Angular and Vue, but it can also just run using standard HTML!
So - What is Storybook?
Simply put - Storybook is a tool which helps you create components in isolation - so you’re not distracted by other issues that may pop up elsewhere in your app. The advantage to this is that you become more aware and more careful on what your component should or shouldn’t do.
Storybook also easily allows you to organise components, and allow you to create various stories or scenarios for each component. For example, stories for a toast notification may include a success message, warning and error message.
You can also write stories for scenarios that shouldn’t happen, but might happen. A bit like error handling. For example, a graph or chart where no data is loaded, or a video player where the video couldn’t be played.
Storybook does this by running a UI in your browser for you to develop and test in.
Below is a screenshot of Netlify’s Storybook - You can view it in full by clicking here.
Component Driven Development (CDD)
If you look into Storybook a bit more, you’ll come across “Component Driven Development” or “CDD” a little bit.
Effectively - You drive your development of your app or project by starting with the smallest components, and then re-use those to create more components, re-using them, and repeating until you end up with much larger components or even your entire page.
This method of working can similarly be compared to the Atomic Design Methodology - Where Organisms are comprised of molecules which is also comprised of atoms.
The basic idea of it is - Create something so small, and re-use it. When we actually do some coding in this example, I’ll explain CDD a bit more.
Installing Storybook
For this example, I’m going to use React as it’s the framework I’d say I’m most familiar with. But as I mentioned earlier, Storybook seemingly seems to work with mostly anything - providing it uses Webpack it seems…
So - to begin with, run the standard npx
command to start a boilerplate React project. Once it’s finished, enter the new directory
npx create-react-app storybook-demo
cd storybook-demo
Installing Storybook is actually quite easy. All it is is one command, and it will take care of everything for us.
npx -p @storybook/cli sb init
This command will do a few things for us:
- Install Storybook - along with all it’s dependencies.
- Create a few boilerplate files and folders, including the config file.
- Add in two scripts to our
packages.json
so we can easily run Storybook - or create a static build of Storybook.
Configuring the Environment
At the moment, Storybook is pretty much ready to go - but there is one change I’d like to make.
Personally, I believe that the best way to organise your file/folder structure is to keep components within their own individual folders, sortof like a bundle. This is just so that you can easily find the style file for your component because its usually in the same directory. As well as if you ever need to drop them in another project - it’s just a case of copying a whole folder.
As part of the installation/setup, Storybook has created a directory under src/stories/
, it contains two demo files - you can delete the whole directory as we’re not using it. And since we’ve deleted the directory, we need to make sure Storybook isn’t to look there.
At the root level of your React project - thee should be a .storybook/
folder, and inside there, you’ll find main.js
- Storybooks main configuration file.
Inside - you should see something like this:
module.exports = {
stories: ['../src/**/*.stories.js'],
addons: [
'@storybook/preset-create-react-app',
'@storybook/addon-actions',
'@storybook/addon-links',
],
};
If you don’t, make sure the stories
array contains “../src/**/*.stories.js”. This basically means any file that ends with .stories.js
inside /src
or one of it’s subdirectories.
Once that’s done - we can get started, to run Storybook, execute the command:
npm run storybook
Storybook will build all the components, and then open your browser to http://localhost:9009/
Creating Components
Now we can get started on creating components!
For the sake of “getting to the point” I won’t really express much with the style or much of the functionality of the components, I’d like to keep Storybook firmly in the picture 😊
Oh - and I’ve decided I’m not going to use any addons despite me using a load whenever I use Storybook. But I thought I’d keep everything very vanilla for now.
So - Lets start small, As we would be following CDD, lets create a button component!
I like to keep all my components in a single folder, so create a new folder inside of src
called components
.
Next, create a new component by creating it’s folder, called Button
, and add it’s javascript file - Button.js
.
// src/components/Button/Button.js
import React from 'react';
const Button = ({ children }) => <button>{children}</button>
export default Button;
For now, our button won’t do anything, and the component is literally just a HTML element - This is because it’s the smallest a component could possibly go, and following CDD - this is the right place to start.
Adding a Component to Storybook
As defined in the setup, we’re going to create a *.stories.js
file that contains all of our stories. So - alongside the new component file we created, we can create Button.stories.js
. Once done, setup the component for Storybook, and create a new story for our button.
// src/components/Button/Button.stories.js
import React from 'react';
import Button from './Button';
export default { title: 'Button' };
export const Primary = () => <Button>Hello World!</Button>;
So what’s going on here?
There are three parts of this code. The first two lines include React and the component we’ve just created. The default export is the Storybook configuration for the component, and lastly the Primary
function is actually our story. The name of the function is important, as it is also used as the name of the story.
Storybook should automatically reload and update, and when you view it, you should see our button within the UI!
Storybook allows us to create as many stories as we like - call these variations or even scenarios. Adding as many components as we can means we can ensure every eventuality is covered, right down to the smallest component.
I’ve gone ahead and created a new CSS file and added in some styling to my button, this would effectively be the primary button on a site or app.
Now, time to create another type of button, a secondary one. I’m going to pass in a new prop of type
, and have one of the classes set to the value. Then I added in some more styling
Once done, create a new story:
export const Secondary = () => <Button type="secondary">Hello World!</Button>;
And Storybook should now have two stories for your Button Component!
Effectively - that’s Storybook in a nutshell. Of course, there is far more things you can do with it - Especially when you get addons involved, but if you’re after a basic use of Storybook, you’re pretty much ready to go.
However, for the benefit of showing off CDD, and how you can handle various user stories or test cases - I’m going to create another component.
I’ve create a new component called ArticleCard
. The code contains a div, the title of the article, a short description, and two buttons, which are our Button
component we just created.
// src/components/ArticleCard/ArticleCard.js
import React from 'react';
import './ArticleCard.css';
import Button from '../Button/Button';
const ArticleCard = ({ post }) => <div className="articleCard">
<h2>{post.title}</h2>
<p>{post.shortDescription}</p>
<Button>Read More</Button>
<Button type="secondary">Add to Reading List</Button>
</div>
export default ArticleCard;
And yes. I do know you’re thinking - But Sam! Why have you created a button component, but not a heading and a paragraph component?! - and yes, it’s a good question. However, I feel that I’d be rather complicating it all if I was to create a paragraph component, which only contained a paragraph element, along with all the required props such as className
or id
. I feel it would be a useless wrapper almost. If I was to create a header component, I’d have a size
prop to determine the size of the header, and that would only switch between the different sizes. The button has a type
prop, which would add a class to it, and further along down the line, I would add a method to handle events for that button which you wouldn’t like to update or manage for every single button that appears on your site, you’d like to only do it once.
Of course, there may be valid reasons to do so, and it is my own opinion at the end of the day, and I might change it later on, however - for this demonstration, there isn’t much reason to create individual components for a heading and a paragraph.
Anywho - back to the code. I’ve created a new Storybook file and added a story for our new ArticleCard
- remember, all we are doing is referencing our component.
// src/components/ArticleCard/ArticleCard.stories.js
import React from 'react';
import ArticleCard from './ArticleCard';
export default { title: 'Article Card' };
export const Default = () => <ArticleCard post={{
title: 'Storybook test article!',
shortDescription: 'This is a test article card for Storybook!'
}} />;
Save, and Storybook should have our new component!
So, thinking about different stories, and scenarios that could happen. What could go wrong here? How about if there isn’t a description provided? We can test that ourselves easily by duplicating our default story, but set the shortDescription to an empty string.
export const NoDescription = () => <ArticleCard post={{
title: 'Storybook test article!',
shortDescription: ''
}} />;
Head back to the Storybook UI - to see what’s happened.
It looks like our code can handle it, Storybook would display any errors that the code would throw. But what if we don’t want it to show anything, what if we wanted it to show something, well - we’d have to update our code to do that!
Back in our React component file, I’ve modified the paragraph tag to either show the description, or a placeholder text.
<p>{post.shortDescription ? post.shortDescription : `Click Read More to view "${post.title}"`}</p>
And Storybook should show our changes!
Now, this is where Storybook gets incredibly useful for me - I can view both stories by flicking between “Default” and “No Description”. Any changes I make to the component can easily be viewed in both stories - so if I make a change, I can easily see if it effects other stories, or even other components.
I’ve updated the styles to the ArticleCard
component - and we’re going to make one last component - an ArticleList
.
// src/components/ArticleList/ArticleList.js
import React from 'react';
import './ArticleList.css';
import ArticleCard from '../ArticleCard/ArticleCard';
const ArticleList = ({ posts }) => <div className="articleList">
<h1>{posts.length} Articles</h1>
<div className="articleList__list">
{posts.map((post) => <ArticleCard {...post} />)}
</div>
</div>
export default ArticleList;
This will contain multiple ArticleCard
components to form them as a list - and the story would have an array of posts/articles:
// src/components/ArticleList/ArticleList.stories.js
import React from 'react';
import ArticleList from './ArticleList';
export default { title: 'Article List' };
export const Default = () => <ArticleList posts={[
{
title: 'ArticleList test!',
shortDescription: 'This is a test article card appearing in a test ArticleList!'
},
{
title: 'Look! A one without a description!',
},
]} />;
After a bit of styling - You should see something like this in you’re UI!
So - now we’ve created a component, to create a component… To create another component - you can see how Component Driven Development (CDD) works, and hopefully you’d understand the positives from doing so. But for me, I become more considering on what to put in a component or not. I’d look at designs and pick out what should be a component.
We could have easily of started from ArticleList
and worked our way down, but that would mean we’d have to rip out some of our code, create a new component, and paste it back in. Doable - yes, but also not as easy as referencing one you’ve all ready made 😊
So now what?
Like I said earlier - we’ve only touched the the surface of Storybook - and it gets even more amazing when we get addons involved. Here are just a few:
- Knobs - Allows you to change component props within the UI and instantly view the changes.
- Actions - View data retrieved from event handlers or props called from your component.
- a11y - View accessibility issues and ways to improve it from the Storybook UI.
- StoryShots - Automatic snapshot testing using Jest.
- Docs - Automatic documentation generation using information such as PropTypes from the component.
All of those are official addons. And there are lots more community addons as well. You can see a list of addons, some official, some not - by visiting https://storybook.js.org/addons/
Read More
- https://storybook.js.org/ - Storybook Official Website
- https://www.learnstorybook.com/ - Online courses for learning how to use Storybook and Component Driven Development.