DEV Community

loading...
Cover image for Loading states with Storybook and Mock Service Worker

Loading states with Storybook and Mock Service Worker

tmikeschu profile image Mike Schutte ・2 min read

So there I am wanting to make Storybook stories for four states of a component that fetches its own data by way of an Apollo Client hook.

The four states are:

  • Loading 🐌
  • Error 🚨
  • No data 📭
  • Data 😊

I have Mock Service Worker (MSW) set up to intercept network calls. It's pretty straight forward to set up the mocks for the two data states (empty and present) with MSW's context.data and the error state with context.errors.

But how do I render a component in its loading state indefinitely for the purpose of viewing it in a Storybook demo?

I didn't find anything existing on the usual suspects of Stack Overflow, GitHub issues, and or here on DEV, so I went back to the MSW docs and found the perfect solution:

context.delay

I tried using Infinity as the duration at first, but to no avail. I figure one hour is more than enough to adequately work on or review the loading state UI.

Here is my final implementation:

graphql.query<GetTagsQuery, GetTagsQueryVariables>(
  "GetTags",
  (_req, res, ctx) => {
    // 1 hour delay to view the loading state
    return res(
      ctx.delay(1000 * 60 * 60 * 60), 
      ctx.data(newGetTagsData({}))
    );
  }
)
Enter fullscreen mode Exit fullscreen mode

And here is all of the relevant story code:

import * as React from "react";
import { Flex, Heading } from "@chakra-ui/react";
import { Meta, Story } from "@storybook/react";
import { worker } from "mocks/browser";
import { graphql } from "msw";

import { GetTagsQuery, GetTagsQueryVariables, newGetTagsData } from "api";

import ViewTags from ".";

export default {
  title: "Features/View Tags",
  component: ViewTags,
  decorators: [
    (story) => (
      <Flex py="20" flex="1" direction="column" justify="center" align="center">
        <Heading mb={4} fontSize="6xl">
          Avett Rx
        </Heading>
        {story()}
      </Flex>
    ),
  ],
} as Meta;

const Template: Story = () => <ViewTags />;

export const Loading: Story = Template.bind({});
Loading.decorators = [
  (story) => {
    worker.use(
      graphql.query<GetTagsQuery, GetTagsQueryVariables>(
        "GetTags",
        (_req, res, ctx) => {
          // 1 hour delay to view the loading state
          return res(
            ctx.delay(1000 * 60 * 60 * 60), 
            ctx.data(newGetTagsData({}))
          );
        }
      )
    );
    return story();
  },
];

Enter fullscreen mode Exit fullscreen mode

This use case comes from an article I'm publishing soon that outlines setting up a project with all of my favorite tools for React development in 2020:

👀 Keep an eye out for it! 👀

Discussion (1)

pic
Editor guide
Collapse
kettanaito profile image
Artem Zakharchenko

Great overview, Mike!

A nitpick, but it's more common to render the "story" exposed in a decorator, instead of calling it as a function:

StoryName.decorators = [
  (Story) => {
    return <Story />
  }
]
Enter fullscreen mode Exit fullscreen mode

Although React components are functions, rendering them as JSX elements is more transparent in terms of what's going on. This is also how the decorators are recommended in the Storybook docs.