DEV Community

Cover image for Loading states with Storybook and Mock Service Worker
Mike Schutte
Mike Schutte

Posted on

Loading states with Storybook and Mock Service Worker

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! πŸ‘€

Top comments (3)

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.

Collapse
 
juice10 profile image
Justin Halsall • Edited

Thanks for writing this blogpost Mike!
I've been doing the same with standard rest as opposed to graphql and unfortunately when switching to a different story everything is still in the loading state. Have you figured out how to deal with this?

Collapse
 
tmikeschu profile image
Mike Schutte

Hey! Yeah I remember running into this (having to hard refresh between stories). Take a look at setting up unique query clients (which will cause a full rerender and fresh requests): dev.to/tmikeschu/accessing-unique-...