revalidation

Complete guide on how to set up Storybook UI with React

Storybook UI is a great tool for iterating on UI designs. It supports the most popular UI frameworks out there, including React. Here is a guide on how to set up Storybook with React in existing or brand-new projects.

Image of a coding work space

Storybook has become the de facto standard for building UI components. Storybook UI is a great tool for iterating on UI components. It supports the most popular UI frameworks out there, including React. Here is a guide on how to set up Storybook v6 with React version 17.

Storybook provides the ability to test UI components in isolation. With Storybook, UI components can be iterated and documented as they are developed.

Because Storybook runs in isolation from the project, it can be hosted and shared with design teams and stakeholders for reviews. This improves the team's feedback speed and overall development speed.

Storybook's version 6 added many features to the previous version 5 while keeping both compatible. Version 6 simplified many things, including defining Webpack configurations, Babel configurations, add-on integrations, and storybook parameters.

React 17

React v17, the latest React release, is not much different from the previous version 16; in fact, according to the React team, React 17 does not add any new features but rather focuses on improving the library's toolchain, in what it termed "preparing for future releases."

One of the updates in React 17 is the ability to code in JSX without the need for React global import. In previous React versions, you must import the React global to write in JSX or TSX, as shown below.

import React from 'react'; // <--- React global has to be imported prior to react v17
export const HelloComponent = () => {
  return <div>Hello World</div>;
};

With version 17, the JSX transformation got improved and lifted away from the React package. Importing the React global within your JSX file is no longer necessary.

The React team worked with the Babel team to add support to the Babel JSX transform plugin, which changed the game.

//React versions 17+, React import not needed.
export const HelloComponent = () => {
  return <div>Hello World</div>;
};

According to React, this new syntax will bring performance improvements and can reduce the overall bundle size. Unfortunately, Storybook version 6 does not support React version 17 out of the box when React global is not imported.

Set up Storybook v6 with React v17

Below are the steps to take when setting up Storybook v6 with React v17.

  1. Upgrade to React version 17
  2. Upgrade to Storybook version 6
  3. Upgrade typescript to support new JSX syntax
  4. Modify Storybook config to support React version 17

We will go into detail about each step.

1. Upgrade to React version 17

The first step is to upgrade to version 17 of React. To upgrade to React version 17, run the command below.

yarn add react@17.0.1 react-dom@17.0.1
or
npm install react@17.0.1 react-dom@17.0.1

After this, you will want to update your codebase to remove all unused React imports within your JSX or TSX files. With React code mod, this process is automated.

Run the command below at the root of your project to remove all unused React imports.

npx react-codemod update-react-imports

2. Upgrade to Storybook v6

If you do not have Storybook v6 currently in your project, upgrade to the latest version by running the sb command, as shown below. The command will upgrade all @storybook project packages to their latest versions.

npx sb upgrade

If you do not have any storybook installations yet, you can install Storybook and initialize it in the project using the Storybook CLI package. Run the scripts below to install and initialize React Storybook.

yarn add @storybook/cli --dev
npx sb init

3. Upgrade to Typescript 4.1+

If you are running typescript in your project, you should upgrade to typescript version 4.1+ and change the jsx compiler options entry value from react to react-jsx to avoid the typescript error below:

'React' refers to a UMD global, but the current file is a module. Consider adding an import instead.

{
  "compilerOptions": {
    "jsx": "react-jsx",
    "lib": ["dom", "es5", "es2015.collection", "es2015.iterable"],
    "allowSyntheticDefaultImports": true,
    "resolveJsonModule": true
  },
  "include": ["src"],
}

4. Modify Storybook config to support React version 17

Storybook version 6 has zero configuration. It lets you do what is necessary for your project. With the React version, we have to use the new JSX transform from the React team.

Install the latest versions of Babel loader, Babel preset react, and other necessary plugins using the command below.

yarn add --dev @babel/core @babel/preset-env @babel/preset-react @babel/preset-typescript @babel/plugin-proposal-class-properties @babel/plugin-proposal-nullish-coalescing-operator @babel/plugin-proposal-optional-chaining babel-loader

After this, we need to set up or modify the main.js file in the storybook folder to support React version 17. Without this, Storybook will error out with the message:

Uncaught ReferenceError: React is not defined. This error is happening because all JSX files in the project no longer import the React global since we upgraded to React 17.

The storybook main.js file defines custom Webpack configurations, Babel configurations, Addons integration, and Stories' path.

In the configuration, we utilize babel-loader, as shown below. Copy the configuration and adjust it to your needs. The main thing below is that we added the runtime: 'automatic' option to @babel/preset-react.

Automatic runtime is a feature available in @babel/plugin-transform-react-jsx version 7.9.0, used under the hood by React's babel preset.

Babel will automatically import the functions needed to transpile JSX to regular JS from react/jsx-runtime library during the build stage with automatic runtime enabled.

// .storybook/main.js
const path = require('path');

module.exports = {
  // defines where storybook will locate our stories
  stories: ['../src/**/*.stories.@(ts|tsx)'],

  addons: ['@storybook/addon-knobs/register'],

  babel: async (options) => {
    return {
      ...options,
    };
  },

  webpackFinal: async (config, { configType }) => {
    // `configType` has a value of 'DEVELOPMENT' or 'PRODUCTION'
    // You can change the configuration based on that.
    // 'PRODUCTION' is used when building the static version of storybook.

    // here we use babel-loader
    config.module.rules.push({
      test: /\.(ts|tsx)$/, <----- babel loader will process all of our stories
      loader: require.resolve('babel-loader'),
      options: {
        babelrc: false,
        presets: [
          '@babel/preset-typescript', <---- support for typescript
          [
            '@babel/preset-react',
            {
              runtime: 'automatic', <---- support for react's new jsx transform,
            },
          ],
          ...................
        ],
        plugins: [
          ['@babel/plugin-proposal-nullish-coalescing-operator'],
          ['@babel/plugin-proposal-optional-chaining'],
          ['@teclone/babel-plugin-styled-components'],
          .........
        ],
      },
    });

    config.resolve.modules = [
      path.resolve(__dirname, '../', 'node_modules'),
      'node_modules',
    ];

    config.resolve.extensions.push('.ts', '.tsx');

    config.stats = 'verbose';

    // Return the altered config
    return config;
  },
};