Simple Example of Storybook

Hello my gorgeous friend on the internet. So here we are going to learn about Storybook with react. We will start by knowing what is storybook? and why you should be using it? and how it helps in development.

As you are here I hope you know the basics of React app development like how to create a simple react app and its basic structure.

The storybook tool is an open-source tool and you can create components in an isolated environment. Storybook is supported by React, Vue and Angular.

So let's start with the Storybook definition "storybook is nothing a but an environment where you test your components user interface and its behavior"

Storybook with react gives completely different environments from which you can easily check the user interface of your components.

Storybook contains many stories represent some component. Story component can be of any size like from simple button in different CSS to big presentation div with different data.

Here's the checklist of storybook with react:

Storybook Environment

Storybook environment looks like this

Step to get Storybook in React App :

Step 1 Create Create App

npx create-react-app (app name)

Step 2 Go inside App Folder

cd (app name)

Step 3 Install Storybook Package

npx -p @storybook/cli sb init

Or

npx -p @storybook/cli sb init --type react

Step 4 Run Storybook

npm run storybook

After installing Storybook package you can directly run it as by default package provides two stories

Storybook package creates a ".storybook" folder and it contains main.js which basically tells storybook which files are stories and also one folder named "stories" which contains all stories. It contains two sample stories.

//main.js
module.exports = {
  stories: ['../stories/**/*.stories.js'],
  addons: ['@storybook/addon-actions', '@storybook/addon-links'],
};

You need to understand the naming convention here. Your story file name should be something like this "(component name).stories.js" then and only then storybook will recognize it.

Default button.stories.js will look like this

//button.stories.js
import React from 'react';
import { action } from '@storybook/addon-actions';
import Button from './Button';
export default {
  component: Button,
  title: 'Button',
};
export const text = () => <Button onClick={action('clicked')}>Hello Button</Button>;
export const emoji = () => (
  <Button onClick={action('clicked')}>
    <span role="img" aria-label="so cool">
      πŸ˜€ 😎 πŸ‘ πŸ’―
    </span>
  </Button>
);

Step 5 First understand the above code and delete two default files from the stories folder. You are not going to need them

Step 6 Now create Simple component name for example Button.js

//Button.js
import React from "react";
const Button = (props) => {
  return <button> {props.buttonName}</button>;
};
export default Button;

Step 7 Create a story file. To create a story file you need to create a file [ Button.stories.js ] inside the stories folder and you need to import component you want to make the story of as shown below.

//Button.stories.js

import React from "react";
import Button from "../src/Button";

export default {
  title: "My Button",
  component: Button,
};

export const ClickMe = () => <Button buttonName="Click Me" />;
export const Save = () => <Button buttonName="Save" />;
export const Delete = () => <Button buttonName="Delete" />;

As shown above you can call component as many time as you want to and pass different props to it.

Make sure you are giving each export a different meaningful name and also a unique title in export default.

Step 8 Now run storybook from terminal

npm run storybook

And your storybook will look like this

Run Storybook from Terminal

That' it! All done simple and plain example of a storybook with react.

Now if you click on Save button then Button name will gate change to Save and same for Delete

See how you can try on different components and this time pass different CSS to component as props. In the storybook environment you can check their look and feel.

Now you know why you should use storybook in react app development.

STORYBOOK WITH KNOBS

The storybook concept doesn't stop here. Storybook has additional functionality called "Addons"

Which gives advance functionalities to storybook and one of them is "Knobs".

So as you have seen before that we pass props as data to stories in storybook but what if you wanna check dynamic data or want to edit props on running environment so for that we use Knobs.

Knobs gives us to modify props data on running stories.

We will go step by step for this one also so let's assume you got simple storybook app up and running

Step 1 install the knob package

yarn add @storybook/addon-knobs β€”dev

Or

npm i add @storybook/addon-knobs β€”dev

Step 2 Add Addon to your main.js file and your main.js will look like this.

//main.js

module.exports = {
  stories: ["../stories/**/*.stories.js"],
  addons: [
    "@storybook/addon-actions",
    "@storybook/addon-links",
    "@storybook/addon-knobs/register",
  ],
};

Step 3 Import-Package inside story file.

So now inside your already created story file [ Button.stories.js ] import the add-on package

import { withKnobs, text } from "@storybook/addon-knobs";

step 4 Add Decorators variable inside export default

export default {
  title: "My Button",
  decorators: [withKnobs],
};

Step 5 use the imported text from package inside button pass props as

{text("Label", "Click Me")}

After doing all the above steps your Button.stories.js will look like this

import React from "react";
import Button from "../src/Button";
import { withKnobs, text } from "@storybook/addon-knobs";

export default {
  title: "My Button",
  decorators: [withKnobs],
};

export const ClickMe = () => {
  return <Button buttonName={text("Label", "Click Me")} />;
};
export const Save = () => <Button buttonName="Save" />;
export const Delete = () => <Button buttonName="Delete" />;
Storybook With Knobs

And you will look get screen something like this

Now let me explain what we did above.

So we got "text" from the package and basically text property gives us an input text area on the storybook panel from where we can change the button name.

So basically I don't have to write two-three export for the same component now and so you can remove both save and Delete button export from story file

If you change the label text property to &quots;Save&quots; then it is going to reflect on the button above instantly.

Just like Text property knobs has many and most useful are as follow

1 text

you have already seen it in the example above

It has a pretty simple syntax.

But first don't forget to import

import { text } from '@storybook/addon-knobs';

For whatever place you want to change value just add

text( default Id, default value)

2 select

Select gives us a dropdown options on the storybook panel and from that, we can change the data on story

To use first import select

import { select } from '@storybook/addon-knobs';

All knobs have the same kind of syntax with little variation so for select syntax is

select( name Of select, Options, Default Value, GroupId )

//example
	
  export const Save = () => {
    const options = {
      Red: "red",
      Blue: "blue",
      Yellow: "yellow",
      Rainbow: ["red", "orange", "etc"],
      None: null,
    };
  
    return <button>{select("Button Name", options, "red", "Button-Id1")}</button>;
  };
Storybook Select

3 object

Object is very important because with the help of object you can pass the JSON data to story and so you can check the actual JSON data on stories

To use first import object

import { object } from '@storybook/addon-knobs';

Syntax of an object is much similar to select property

object(label, defaultValue, groupId);

//Example 

export const Delete = () => {
  const defaultValue = {
    backgroundColor: "red",
  };
  const value = object("style", defaultValue, "styleId-1");
  return <button style={value}> Click me</button>;
};
Storybook Object

You can try out others like array, color, number, boolean.

STORYBOOK WITH REDUX STORE

Now after using storybook at advance level. The question arises that can we get actual redux store data in the storybook? So we can test components with real data coming from different API's and sources.

Answer is Yes, we can get redux store data in a storybook. To get redux data we need to make a few changes to stories files and add some files

Before we proceed I am assuming that you know how redux store works and saga middleware because we are gonna use saga middleware to get data.

Steps To Get Redux store data in Storybook

1 Bootstrap File

you need to create a bootstrap.js file in src/ directory.

//bootstrap.js
import React from "react";
import { Provider } from "react-redux";
import { applyMiddleware, createStore } from "redux";
import rootReducers from "./reducers/index";
import createSagaMiddleware from "redux-saga";
import RootSaga from "./sagas";

//saga middleware
const sagaMiddleware = createSagaMiddleware();

//add any other middleware if you want in middleware array below
export default ({ middleware = [ sagaMiddleware] } = {}) => {
  const store = createStore(rootReducers, applyMiddleware(...middleware));
  sagaMiddleware.run(RootSaga);
return [
    store,
    renderFunction => <Provider store={store}>{renderFunction()}</Provider>
  ];
};
Storybook Create Bootstrap.js File

So in the above file, we are returning render function which basically puts any provided function inside <Provider> tag and gives store access to it

2 Now you just need to import the bootstrap file in the story file. As we are getting data from store and if your application components work with store data then you don't need multiple story files. You can just call every component inside a single file.

//all.stories.js
import React from "react";
import { storiesOf } from "@storybook/react";
import OrderDetails from "../src/core/OrderDetails";
import PlceOrder from "../src/core/PlaceOrder";
import Customers from "../src/core/Customers";
import Items from "../src/core/Items";
import bootstrap from "../src/bootstrap";

// bootstrap object contains store and render 
const [store, render] =   ();
store.dispatch({
  type: "LOAD_DATA"
});

storiesOf("Story With Store", module)
  .addDecorator(render)
  .add("OrderDetails", () => {
    return <OrderDetails />;
  })
  .add("PlceOrder", () => {
    return <PlceOrder />;
  })
  .add("Customers", () => {
    return <Customers />;
  })
  .add("Items", () => {
    return <Items />;
  });

So you can see we triggered the "LOAD_DATA" event from the store and so saga gets trigger. On trigger saga method stores data inside redux store. You can see it in below code

//saga/index.js
import { all, put, takeEvery, call } from "redux-saga/effects";

function* fetchData() {
 const API = "http://www.json-generator.com/api/json/get/cexsZAdSUO?indent=2";
  const response = yield call(fetch, API);
  const data = yield response.json();
 yield put({ type: "COPY_DATA_TO_STORE", payload: data });
}

function* watchLoader() {
 yield takeEvery("LOAD_DATA", fetchData);
}

export default function* rootSaga() {
  console.log("rootSaga");
  yield all([watchLoader()]);
}

And we passed the "render" object coming from bootstrap to .decorator(). In simple words we are telling stories to go through this decor before submitting it to the storybook environment.

Now you know pretty much about the storybook. Personally I use it for every project as it helps a lot.

Simple one more fact that changes made in the project reflects on the storybook environment
[ npm run storybook ] much master than on project start [ npm start ]

I hope you got everything you were looking for and have a nice day !!

Connect with Coditude

We believe successful solutions stem from meaningful partnerships and collaborations. We'd love to hear about you and build together ground breaking solutions.