DevGuide: How to Integrate Storybook with Hydrogen

  • Technology
  • October 19, 2022
  • by Drew Garratt

Shopify’s Hydrogen framework has been an exciting addition to the headless landscape. Since its release a few short months ago, the framework has matured quickly, responding to feedback from developers and merchants alike. But if you’re a developer and you’ve been working with front-end frameworks, mainly component-based ones, for a while, you’ll quickly ask yourself, “Where is an example of this being used with Storybook?” – We’ve got you covered. We’re giving you a developer’s step-by-step guide to using Storybook with Hydrogen.

Note: This article is specifically geared towards Hydrogen developers and explores how brands can use Storybook with Shopify’s Hydrogen framework. For those in a hurry and want to explore a working example, please find our demo repo here

Why doesn’t it just ‘work’?

Like Hydrogen, Storybook has transformed in the last few months. Concentrating on developer experience and modern tooling, the release of Storybook 6.5 gave us Interaction testing, Figma Plugin, Faster Webpack, and the Vite builder.

Vite is the development server and bundle engine that powers more and more development projects. It’s at the heart of Shopify’s Hydrogen framework powering the development experiences and SSR rendering on Shopify’s edge network.

So with this engine now being natively supported by Storybook, you’d expect getting this set up would be easy. Let’s test that theory with a demo store - with this in place, it should be as simple as yarn dev to see a test store.

1. Let’s start by creating a Hydrogen demo store

yarn create @shopify/hydrogen

2. Choose a template

? Choose a template
> Demo Store
  Hello World

3. Choose Typescript

? Choose a language
  JavaScript
> TypeScript

4. Name your store

Name your new Hydrogen storefront
> storybook-for-hydrogen
  ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔

Now we have the demo store, let’s go ahead and install Storybook with a Vite builder. Storybook has concentrated on developer experience, so this process can be started with a single command.

npx sb init --builder @storybook/builder-vite

Storybook will automatically detect the presence of React and go ahead and add the relevant dependencies.

You will also be promoted to run the ESLint migration, which we advise you do.

? Do you want to run the 'eslintPlugin' migration on your project? › (y/N)

Now we should be able to go ahead and run Storybook with yarn storybook.

Well… that didn’t work out so good, did it?

Unfortunately, this throws an error, but it’s quickly resolved. The core of this error is Vite itself. With builder-vite v0.2.0 upward, Storybooks builder depends on Vite 3.x while, at the time of writing, Hydrogen still uses Vite 2.x.

So the fix is simple enough, pin the builder to the last version that supported Vite 2.x v0.1.41. Open your package.json file and amend @storybook/builder-vite to use v0.1.41.

"@storybook/builder-vite": "0.1.39",

Run yarn install, and let’s try yarn storybook again. Everything runs as it should this time, and, as a result, Storybook will launch.

So, with a little bit of dependency pinning, it’s possible to run Storybook inside the Hydrogen Vite config. However, soon this step won’t be necessary as Hydrogen moves to Vite 3.0.

Hydrogen inside Storybook?

Storybook is useful to have alongside your project. But its real utility is breaking your UI into components that can be previewed, tested, and composed outside your application. So let’s try importing an existing Hydrogen component - ProductCard.client.tsx seems like a good candidate.

We need to create a new story file alongside the existing component, ProductCard.stories.tsx. Let’s look at the basic layout of the file…

import {ComponentStory, ComponentMeta} from '@storybook/react';

import {ProductCard} from './ProductCard.client';

// More on default export: https://storybook.js.org/docs/react/writing-stories/introduction#default-export
export default {
  title: 'Hydrogen/Components/Cards/ProductCard',
  component: ProductCard,
} as ComponentMeta<typeof ProductCard>;

// More on component templates: https://storybook.js.org/docs/react/writing-stories/introduction#using-args
const Template: ComponentStory<typeof ProductCard> = (args) => (
  <ProductCard {...args} />
);

Once our basic story is importing ComponentStory and ComponentMeta for Storybook and our ProductCard component from the existing component file. We then simply declare our new story. In this example, we’ve declared our title to match the path to the component file itself. Now we can go ahead and declare our template to output our component.

But, before we rerun Storybook, we need to set some default data for our component. We do this by binding a template to our component.

export const Default = Template.bind({});
// More on args: https://storybook.js.org/docs/react/writing-stories/args
Default.args = {};

To create a default set, we suggest using React Dev tools to capture the props from the demo store.

export const Default = Template.bind({});
// More on args: https://storybook.js.org/docs/react/writing-stories/args
Default.args = {
  product: {
    id: 'gid://shopify/Product/6730850828344',
    title: 'The Hydrogen Snowboard',
    publishedAt: '2022-06-09T21:22:48Z',
    handle: 'snowboard',
    variants: {
      nodes: [
        {
          id: 'gid://shopify/ProductVariant/41007289630776',
          image: {
            url: 'https://cdn.shopify.com/s/files/1/0551/4566/0472/products/Main.jpg?v=1655932274',
            altText: null,
            width: 3908,
            height: 3908,
          },
          priceV2: {
            amount: '600.0',
            currencyCode: 'USD',
          },
          compareAtPriceV2: {
            amount: '649.95',
            currencyCode: 'USD',
          },
        },
      ],
    },
  },
  className: 'snap-start w-80',
};

You may find that typescript complains about some of these types. That itself isn’t a great concern here, but we should note that Hydrogen still has some fairly basic problems with its typing of objects like images.

Now we have a component and some default data, let’s try accessing our component inside Storybook.

So at a basic level, it looks like our import isn’t supported

At a basic level, it looks like our import isn’t supported. This first error is actually related to our use of typescript. The terminal output will show you that the ‘~/’ import path has not been recognized.

Plugin: vite:import-analysis
  File: /Users/drewgarratt/Development/storybook-for-hydrogen/src/components/cards/ProductCard.client.tsx
  16 |  import clsx from "clsx";
  17 |  import { flattenConnection, Image, Link, Money, useMoney } from "@shopify/hydrogen";
  18 |  import { Text } from "~/components";
     |                        ^

Storybook has a plugin to help resolve this issue, allowing it to import the project TSconfig.

const { mergeConfig } = require("vite")
const { default: tsconfigPaths } = require('vite-tsconfig-paths')

module.exports = {
  viteFinal(config) {
    return mergeConfig(config, {
      plugins: [
        tsconfigPaths()
      ]
    })
  },
  stories: [
    "../src/**/*.stories.mdx",
    "../src/**/*.stories.@(js|jsx|ts|tsx)"
  ],
  addons: [
    "@storybook/addon-links",
    "@storybook/addon-essentials",
    "@storybook/addon-interactions"
  ],
  framework: "@storybook/react",
  core: {
    builder: "@storybook/builder-vite"
  },
  features: {
    storyStoreV7: true
  },
}

We can then amend our .storybook/main.js to use this plugin and understand our import paths.

yarn add vite-tsconfig-paths -D

With this in place, let’s try again.

So at a basic level, it looks like our import isn’t supported

We’ve hit our next roadblock. Hydrogens components use components like <Link> that will be assumed to run inside wider app-wide contexts like <Router>. If we import the router and wrap it around this component, the router itself will fail. Because it, too, depends on HOC and data passed during Hydrogen build.

But we’re not ending things there.

Decorators for Hydrogen

It’s very common in frameworks to use Global decorators to stub HOCs on which framework components depend. If you’ve worked with React Router or Next.js, you are likely familiar with the process.

Some 3 hours after its release, Hydrogen 1.5.0 made their internal test provider <ShopifyTestProviders> publicly available via import {ShopifyTestProviders} from '@shopify/hydrogen/testing'. This should allow us to create a simple decorators file for use in our config. Inside the .storybook folder, we created the following decorators.tsx.

import React from 'react'
import {DecoratorFn} from '@storybook/react'
import {ShopifyTestProviders} from '@shopify/hydrogen/testing';

export const withShopifyTestProvider: DecoratorFn = (Story) => (
  <ShopifyTestProviders>
    <Story />
  </ShopifyTestProviders>
)

Updating our preview.js file to preivew.ts and adding the following:

import { withShopifyTestProvider } from './decorators';

export const parameters = {
  actions: { argTypesRegex: "^on[A-Z].*" },
  controls: {
    matchers: {
      color: /(background|color)$/i,
      date: /Date$/,
    },
  },
}

export const decorators = [withShopifyTestProvider]

By doing things this way, we can import and use a TSX component as a global decorator. To accompany our new global decorator, we have to make one last change to the Storybook main config file and add one additional package.

We need to add two variables to the Storybook config (.storybook/main.ts) that the ShopifyTestProviders HOC checks to enable its functionality.

const { mergeConfig } = require("vite")
const { default: tsconfigPaths } = require('vite-tsconfig-paths')

module.exports = {
  viteFinal(config) {
    return mergeConfig(config, {
      plugins: [
        tsconfigPaths(),
      ],
      define: {
        __HYDROGEN_TEST__: true,
        __HYDROGEN_DEV__: true,
      }
    })
  },
  stories: [
    "../src/**/*.stories.mdx",
    "../src/**/*.stories.@(js|jsx|ts|tsx)"
  ],
  addons: [
    "@storybook/addon-links",
    "@storybook/addon-essentials",
    "@storybook/addon-interactions"
  ],
  framework: "@storybook/react",
  core: {
    builder: "@storybook/builder-vite"
  },
  features: {
    storyStoreV7: true
  },
}

Finally, we need to add an additional dev dependency to our package, used by our ShopifyTestProviders. Simply run yarn add faker@5.5.3 -D

The current ‘gotcha’

If you’re still with us, you might gather there is a catch coming.

While 1.5.0 has made ShopifyTestProviders available for import, one of the individual test utilities imported by the provider currently depends on the faker library, but it does so without declaring dependency. This causes any component that imports ShopifyTestProviders to fail.

Note: Until our PR for a fix is merged, we have installed a patch to remove this undeclared dependency in this demonstration. 

With everything in place, only one piece remains. To ensure that we use the same global styling as the main application, simply import the index style sheet into preview.ts.

import '../src/styles/index.css';
import { withShopifyTestProvider } from './decorators';

export const parameters = {
  actions: { argTypesRegex: "^on[A-Z].*" },
  controls: {
    matchers: {
      color: /(background|color)$/i,
      date: /Date$/,
    },
  },
}

export const decorators = [withShopifyTestProvider]

The result

With our new global decorator in place and styles configured, we are ready to once again run yarn story-book and finally, we can see our Hydrogen component output.

Please see our GitHub repo to check out the above example in code.

And for more examples of how we are working with headless in Shopify, check out our recent case study on how Pavers went headless with Shopify Plus and our in-depth guide to headless with Shopify.


Authors

Drew Garratt

Drew has worked as a developer for the last ten years. Transitioning from an early career as an art-worker through web design into development he has a wide range of experience. He's proud to have worked with British Gas, Vision Express and Countrywide on a variety of projects. A keen advocate of headless development and PWA.


Recommended reading

05 July 2022

Headless

Shopify's Hydrogen and Oxygen - What You Need to Know


10 March 2022

Headless

Creating the Perfect Tech Stack for Your Headless Build


Popular articles

21 November 2022

International

Internationalization with Shopify Markets Pro


10 November 2022

Strategy

A Closer Look: Goose & Gander’s Black Friday Mystery Box


01 November 2022

Technology

Elevating your B2B e‑commerce with Shopify B2B


20 October 2022

Strategy

13 Scary E-Commerce Mistakes


20 July 2020

Technology

How to Sell Internationally with Shopify


02 November 2020

Technology

Migrating from Magento to Shopify Plus


19 October 2021

Headless

Headless Commerce Using Shopify Plus


25 September 2020

Technology

Why Shopify Plus?


02 March 2020

Design

Biggest Brands on Shopify


22 April 2020

Strategy

35 Ways to Improve Your E‑Commerce Conversion Rate


01 March 2019

Technology

Shopify Plus: Multi-Store vs Multi-Currency


07 April 2022

International

Brands Selling Internationally on Shopify


09 October 2019

Strategy

CCPA and Shopify: What it is and How it Affects my Store


14 August 2020

Strategy

Everything you Need to Know About ADA and Shopify


21 July 2019

Design

101 Best Shopify Stores for Design Inspiration


Be the first to hear about what’s hot in e‑commerce and Shopify Plus. Straight to your inbox.

By providing your email, you agree for us to contact you via email with e‑commerce advice. Your data is stored securely and we never pass it on to third parties.