Storybook Addon Apollo Client

10.0.0 · active · verified Tue Apr 21

This Storybook addon integrates Apollo Client into your Storybook stories, allowing developers to effectively test components that interact with GraphQL APIs. It is currently on version 10.0.0, which supports Apollo Client v3/v4 and Storybook 10+. The package has a relatively fast release cadence, often aligning its major versions with new releases of Storybook and React, requiring users to carefully manage their `storybook-addon-apollo-client` version to match their Storybook and Apollo Client installations. A key differentiator is its ability to expose GraphQL queries and responses in a dedicated Storybook addon panel, simplifying the debugging and visualization of data interactions within isolated components. It primarily achieves this by leveraging Apollo Client's `MockedProvider` and a custom Storybook decorator.

Common errors

Warnings

Install

Imports

Quickstart

This quickstart demonstrates the required `preview.ts`/`preview.tsx` setup for `storybook-addon-apollo-client` v10.x. It includes a decorator that integrates Apollo Client's `MockedProvider` and sets up event listeners to display GraphQL queries and responses in the Storybook addon panel, crucial for visualizing mock data interactions within your components.

import type { MockedResponse } from '@apollo/client/testing';
import { MockedProvider } from '@apollo/client/testing';
import { addons } from 'storybook/internal/preview-api';
import type { Preview } from '@storybook/react';
import { print } from 'graphql';
import { useEffect } from 'react';
import type { ApolloClientAddonState } from 'storybook-addon-apollo-client';
import { EVENTS } from 'storybook-addon-apollo-client';

const getMockName = (mockedResponse: MockedResponse) => {
  if (mockedResponse.request.operationName) {
    return mockedResponse.request.operationName;
  }

  const operationDefinition = mockedResponse.request.query.definitions.find(
    (definition) => definition.kind === 'OperationDefinition',
  );

  if (operationDefinition?.name) {
    return operationDefinition.name.value;
  }

  return `Unnamed`;
};

function stringifyOrUndefined(value: unknown) {
  try {
    return JSON.stringify(value, null, 2);
  } catch {
    return undefined;
  }
}

function createResultFromMocks(mocks: MockedResponse[], activeIndex: number): ApolloClientAddonState {
  const mock = mocks[activeIndex];
  if (!mock) {
    return {
      activeIndex: -1,
      options: mocks.map(getMockName),
      query: undefined,
      variables: undefined,
      extensions: undefined,
      context: undefined,
      result: undefined,
      error: undefined,
    };
  }
  return {
    options: mocks.map(getMockName),
    activeIndex: activeIndex,
    query: print(mock.request.query),
    variables: stringifyOrUndefined(mock.request.variables),
    extensions: stringifyOrUndefined(mock.request.extensions),
    context: stringifyOrUndefined(mock.request.context),
    result: stringifyOrUndefined(mock.result),
    error: stringifyOrUndefined(mock.error),
  };
}

const preview: Preview = {
  decorators: [
    (Story, context) => {
      useEffect(() => {
        const { mocks = [] } = context.parameters.apolloClient || {};
        const channel = addons.getChannel();

        const handleRequest = (activeIndex: number) => {
          const state = createResultFromMocks(mocks, activeIndex);
          channel.emit(EVENTS.RESULT, state);
        };

        handleRequest(mocks.length ? 0 : -1);

        channel.on(EVENTS.REQUEST, handleRequest);

        return () => {
          channel.off(EVENTS.REQUEST, handleRequest);
        };
      }, [context.parameters.apolloClient]);

      if (!context.parameters.apolloClient) {
        return <Story />;
      }

      return (
        <MockedProvider {...context.parameters.apolloClient}>
          <Story />
        </MockedProvider>
      );
    },
  ],
};

export default preview;

view raw JSON →