What is Storybook?
When it comes to development, testing, and documenting of components and Design Systems, Storybook is the perfect tool. It integrates with pretty much any front-end library out there, and in this video, I'm going to show you specifically how to use Storybook with React.
Welcome back to Web Dev Simplified. My name is Kyle and my job is to simplify the web for you so you can start building your dream project sooner. And in this video, I'm going to show you how you can integrate Storybook into any React project.
Now this is actually the project we're going to be building out in this video, and as you can see, it's super clean, really nice looking which is great about Storybook. We have documentation for every single component that we're going to be building that's automatically generated for us, or we can customize as we need. As you can see, we have a bunch of different types of components, like we have a red button, a green button, large, small, a button with a longer label. And we have this stack component that can do horizontal, vertical stacking, can do different spacing. We can wrap with a stack and we can have an empty stack.
And best of all is we can modify all of these components so we can say, you know what, I want to have more children, or I want to have more or less spacing, or I want it to be column based, row based, add in wrapping so if we have more children it's going to wrap or it's not going to wrap. So that's the really nice thing about Storybook is it allows you to do all this customization and it's really easy to implement.
Project Setup and Storybook Initialization
So now in order to get started, let's look at the project we're going to be using. All I have is a really super simple basic React application. I just ran npx create-react-app and I just made the app completely blank and the index here is also pretty much blank. And I just have two components. I have a button component here and I have a stack component, the two components that you saw us testing inside of Storybook.
And essentially what I want to do in this video is show you how to implement Storybook so you can test these components, which makes development easier, you can document them, and all the stuff that I showed at the beginning. Because right now we're not using these components anywhere. You can imagine that we just built these components and we want to be able to test them and view them in an environment that's not stuck somewhere in our app, because our app may be huge and adding in this component is kind of difficult to do for testing. So I want to be able to develop in its own environment, which is where Storybook comes in.
So to get started with Storybook, we need to run Storybook to initialize it. We can just say npx sb init, and what this is going to do is it's going to download all of the Storybook information into our application. And the important thing to note is you need to do this after you create your application. So if you're doing a React app, you need to do it after you create your React app. Or if you're doing Angular, Vue, whatever it is, you need to be able to create the app first and then run the initializer after you create the app. And it's going to come in, it's going to download all the libraries you need, add a bunch of information to like your package.json and so on. That way you can actually use Storybook. And I'm going to come back to you once this finishes.
Exploring the Storybook UI & Project Structure
Now that just finished downloading everything and you're going to notice we essentially have two new folders. We have this .storybook folder which contains our main.js which is like a config file essentially, and a preview.js which allows us to create some global different configurations. So these are just like config files for the most part. You don't really have to worry about them too much.
Then we have this stories folder. This is where all of your Storybook code is going to go. Right now we have a bunch of different example code inside of here.
And then finally, the most important thing is in our package.json, it actually added a script called storybook which we can run. So we can say npm run storybook and that's just going to start up Storybook and it's going to open it up inside of our web browser so we can start interacting with the example that Storybook has given us with all these stories inside of here.
And the important thing to note is when you're creating a Storybook component, so you're creating pages for Storybook, everything is going to be done inside this stories folder by default. You can change this if you want but I kind of like having it in this folder as is. So now that's going to open up and I'll just drag this over to my main screen so we can see. And as you can see we have this example section and in here we have an introduction, and this is just a documentation page right here. We have this button component here which as you can see, we can kind of loop through different types of buttons, and we can even come in here, we can say, you know what, we don't want this to be primary, we do want it to be primary, as well as some documentation on this button. As you can see, we have a header section, and we have a page section. These are all the default pages and components that we have inside of Storybook. And if we open up our React application, you can see we have our button.stories.js, header.stories.js, page.stories.js, and then this introduction.stories.mdx right here. So those are kind of all the different things being mapped to Storybook.
So what I want to do is I just want to delete all of this because this is a lot of information to look at at first, and I just want to start by creating our stories from scratch. So we're going to delete everything inside this stories folder and we're going to create our very own story. So we're just going to call this button.stories.js. The important thing is that you need to put this .stories in here before your .js and that's going to tell Storybook this is going to be a page that is an actual story. And then we can call it whatever we want. We're doing this for our button so we're going to call it button.
Creating Your First Basic Story
Now inside of here, we actually need to set up our story. The way this works inside a Storybook is you just need to export a default object, and this object is going to be your actual story. So in our case, we need to give it a title, and the title here is going to be for example, Button, so we can just say Button right here. And then we also need to give it the component that we're going to be rendering in this story, and we're going to be using our Button component. So let's just make sure up top here we import our Button component from that component library we created. So ../../components/Button, and that's just this component right here. So now we're saying, hey, this has a title of Button and we're going to be rendering out the component which is our Button component.
Then we need to tell Storybook what our different stories are. If you remember when we looked, we had the primary or large or small and so on as different types of stories inside of this button category. So to create an actual named story, all we need to do is export a function that is going to return our component. So we'll come in here, we'll say export const, and whatever name we give this thing is going to be the name of our story. So if we call this Red, we're going to have a story named Red inside of the Button folder. And we're just going to set that equal to a function and this function is going to return our Button component. We're going to give it a label that just says Press Me, and we're going to give it a background color which is going to be red. And then let's just make sure that we close off this button.
And now we're going to have a story called Red inside of our button folder. And if I just drag over our Storybook here, you can see we have the Button folder and inside of that we have the story named Red, which is the export that we just created. And as you can see, we have documentation for this button, so we can see our label is a string, background color is a string, size is small, medium, or large, and this on-click is a function.
And you're probably wondering exactly where this documentation comes from, and that actually comes from the prop types that I have defined. So if you go inside of this button here, you'll see at the bottom we have propTypes defined and it says our size can be one of these three, background color is a string, label is a string, and our onClick is a function. So if you have prop types in your application or you're using TypeScript, Storybook is going to be smart enough to look at that and be able to assume the types of all of your different props, which is really nice. Also, if you're unfamiliar with prop types, I have an entire video covering it. I'll link in the cards and description for you so you can check that out.
Now, let me just drag that over real quick so we can take a look at this again. If we go back into the canvas, you'll notice we have this action section, and when I click on this button, nothing's happening in the action section. So we're going to need to implement that. And also this control section down here, none of it is configurable. It says the story is not configured to handle controls. So that's the main thing I want to work on next is how do we make it interactable, which is the part that makes it so powerful.
Adding Interactivity with Controls & Actions
Now the actual code needed to set up this interactivity is really quite simple, but it's a little bit confusing to understand. The first thing that we need to do is we need to set up essentially a base function. We're just going to set up a function we're going to call it our Template because it's the template all of our different stories we create are going to follow. And this Template is just going to be equal to a function that takes in some arguments, and those arguments are just going to go into our button. So we're going to take our button and we're just going to pass in all of the different arguments and render out our button.
So really all that we've done right here is we've created a function that returns our button and passes all the arguments to it. So this would be like passing in the props to our button essentially. And then what I want to do is I just want to copy this function every single time I use it. So for Red down here, I just want to copy this Template function. And to copy a function, we can just use the bind method and pass it in an empty object, and this is going to copy the Template function. So now we have a brand new version of our template function right here for our Red. And then we can just say Red.args is equal to and define our arguments. So we have a background color which we know is going to be red, and let's just say we have a label here and this label is going to be Press Me. And then finally, we're going to give it a size, which is medium. And we can just save this real quick, and we still have our button rendering over here.
So if we go to the add-on section, you can see we have our controls and now we can actually modify these controls. So I could make this a large button, and the button has become a large button now. Or I could change to a small button, and now we have a smaller button. And if I just expand this to make it a little easier to see, you can see if I change the color here to green, you can see our button is green. We make it a large button, medium, small, change the text inside of it. All of this is 100% configurable. And when I click on this button, you notice in this Actions tab, it's actually logging out our on-click event.
Now this is something that Storybook is actually smart enough to see that this prop is called onClick so it assumes that it is going to be linked up to some type of action such as clicking. So it automatically does that linking for us. But if we have for example here a prop in our button that's titled something different, so example we change onClick here, let's change this to handleClick. I'm just going to change that in all places, put it here and down here. Now Storybook is not going to know that this is for handling our click event. So if we just drag this back to be bigger and we click on this, you're going to notice we no longer get any actions being fired.
So we need to link that up. So inside of our story here, we have something called argTypes. And the argTypes is essentially how we define the different types of all of our arguments and be able to define things such as actions for onClick. So we want to take our prop, we just give it the prop name in our case which is handleClick, and then we want to say what action this does. And this action just takes in a label. We can call this whatever we want. Let's just call it handleClick right here. So all we're doing is saying, hey, this handleClick prop that we're passing in, it is an action, it's going to be doing something, so we want to log out when it does something, when it's called essentially. So whenever this handleClick is called, we want to log it in the action section. So now if I click this a bunch of times and I go over to actions, you can see we printed out handleClick. And that's just this label here. I could call this whatever I want. And now when I click on this button, you can see it's just logging out that label and it's also passing along the information that is passed to that function. And since we're just passing it an event, it's just taking all of the event information.
Building Multiple Story Variations
Now if we wanted to create multiple stories, all we need to do is copy this current story and just create a brand new one. We're going to call this one Green, and instead of being a red background color, we're going to give it a green background. So our Green.args is going to be green here.
And let's copy down this again and we're going to do something for Small. So we're just going to change Green here to Small because that's going to be the name of this story that we're creating. Background color, let's change it back to red, and we're going to change the size to small.
I'm going to do the exact same thing but for Large. So we'll just come in here, we'll say Large, Large, and we're going to change the size to large.
And then finally, we'll do one more and this was just going to be for a long label. And we're just going to take our label and we're just going to make it a really long label. And we'll change the size back to medium.
So now if I just expand this out, you can see we now have our red, our green, our small, our large, our long label. And for some reason our red is actually shown up as green. And if we just reset this down here, you see this reset button in the bottom right, that's going to reset all the changes we made because we actually changed the background color, we changed the label and so on. So if we just make sure that we reset it, it's going to go back to whatever the default is. So if we change this, click reset, it goes back to all the default options.
Now this right there is how you create the most basic of all the different types of stories. And then next we're going to talk about our stack component which is going to be a little bit more involved. So let me just bring this over. We're going to go all the way to the top here. And the first thing I want to do is I want to change our title to be nested inside of a Components folder, because we're going to have a Components folder that contains our Button, our Stack, and so on. So we're going to nest this inside that folder.
Advanced Story for a Stack Component
And then what I want to do is I just want to create a brand new story which is called stack.stories.js. And inside here we need to do essentially the same thing. I'm going to copy all the section at the top, paste it in, but instead of a button, we're going to be using our stack. So I'm just going to select where Button is, replace that with Stack in all locations. And down here for our template, we want to render out our Stack. Also our argTypes here, we don't care about this because there's no click event listeners to worry about, so we can change that. And then our Button.stories, let's just change this to handleClick since it makes a little bit more sense.
So now if I go back over here, expand this out a little further, you can see we have a Components folder and inside that we now have our button, so we kind of have those nested inside each other, which is nice so we can expand, collapse all of them at the same time. And if you have a larger application, this can make organizing things so much easier.
Now if we go ahead and we look at our Stack component, we can kind of see one thing interesting. If we open up our Stack component, you'll see it takes in children and then it's going to take in spacing, direction, and a wrap variable. But the main thing that's important is it takes in children, because we're just wrapping all of the children inside of a stack that gives us some spacing and a direction for everything. So inside of our story, we need to essentially add children inside of our stack.
And I want to be able to determine the number of children being passed into our stack with the add-ons over here where we can actually, you know, change the label for example. I want to be able to change the number of children, but the number of children is not a prop for our stack. So we need to add that as an additional argument we can pass into Storybook. So to do that, again, we're going to use argTypes. And inside of here we want to create a brand new argument. We can call it whatever we want, I'm just going to call it numberOfChildren. And this numberOfChildren is going to have a type so that Storybook knows what this is. We're going to give it a type of number, and we're also going to give it a default value. So by default this is going to be set to four. So we're going to have a numberOfChildren, which is a number, and the default value for that is four.
Then inside of our template, what I want to do is I want to essentially get the numberOfChildren out of here, and then all of the rest of our arguments. And the reason I'm doing that is because numberOfChildren I don't need to pass it to my stack, I need to use that to actually get the children to put inside of the stack. So here let's put this on a new line so we can actually see this a little bit better. We're going to make our stack so it's going to have children inside of it. And the children are going to be based on this numberOfChildren here. So to get the numberOfChildren into an array, what we can do is we can just say Array and pass it in the number of children. And I want to convert this to an array where it has all of the keys essentially. So I want to get the index of the array, so we can say .keys(). This is going to give us the index of all the different array elements. And then I can just map that into an array by using triple dots here, spread it out. So now this just is an array that goes from zero all the way to numberOfChildren. So if numberOfChildren is three, this will be an array that has zero, one, and two inside of it. Then what we can do is we can map over that and we're going to get a number. And inside of here, all we want to do is return a component that's going to act as our child. It's going to be a placeholder. So we're going to use a div. I'm going to give it a style here, and this style is just going to be a width of 50 pixels. So say width 50 pixels, height 50 pixels, we're going to give it a background color which is going to be red. And in order to put the text inside of it, let's just do a quick display here of flex and we're going to justify the content in the center and we're going to align the items in the center. And that should be all we need to do. Now what I want to do is inside of this div, I just want to put the n that we're getting, so this is just the number. And I'm going to add one to this number just so it starts at one instead of zero. So we're just rendering a div essentially. So we created our stack and then for each child number, so for the numberOfChildren, we're putting a red box inside of the stack.
And then what we can do down here is just export our story as normal. So let's do a horizontal stack. We'll just say Horizontal. And that's going to be equal to our template.bind. That's to duplicate that function. Make sure this is a constant variable. And then for our arguments, we can just say Horizontal.args and we want to say that our direction is going to be equal to a row so it's going to be horizontal. We want spacing equal to two. So we'll say spacing is two here. And then finally, we're going to set our wrap to false.
So now we have this horizontal component. So what I can do is I can make this a little bit wider. We can see over here in our Stack, we now have a horizontal stack and it's rendering out four children by default. I can change that to render out more and more children. And as you can see, they're not wrapping, but I could change wrap to true and now they're going to wrap. Or I can go in the column direction, row direction. I could increase my spacing if I wanted, I could decrease my spacing and so on.
The important thing to note about all of this is that as you could see here, we needed to pass an additional argument, an argument that's not at all needed anywhere in our component. It's only needed for Storybook. So we define that inside of our argTypes and then we use that in our template right here to use that argument, but pass all the additional arguments that the stack actually needs to the stack and only pull out the ones that it doesn't need, which in our case is numberOfChildren.
Finalizing Stack Stories & Conclusion
Now let's just create a few more different stories here so we can kind of really see how this works. For example, we can make a vertical story. This one's going to be in the column direction. We can create another story that's going to be for what happens when there's no spacing. So we'll say NoSpacing and we're going to just make this one a row and we'll change spacing to zero.
Let's do another one for where we want to wrap, so we'll say WrapOverflow. And this is going to be wrapping set to true. And let's put our spacing back to normal. And in order to demonstrate how wrapping works, let's set the number of children to a large number, such as 40. That way we always guarantee that there's going to be some type of wrapping.
And then finally, I want to just do what happens when there's an empty container. So for an empty container, numberOfChildren will be set to zero. We can get rid of wrapping here, it doesn't really matter because there'll be no children.
So now if I save this and I expand this over, you'll see we have a bunch of different options. We have our horizontal one, and if we just make sure that we reset it to what it is, you can see it's just four. Our vertical, which is four vertically. No spacing, as you can see there's no space between our components. We have wrapping turned on. And then we have an empty one, which just has nothing inside of it.
And that's all there is to Storybook. If you enjoyed this video, you should definitely check out my other React content, including that React props video linked over here, and subscribe to my channel for more videos just like this. Thank you very much for watching and have a good day.