Create a ReactNative StorybookProvider

  • typescript
  • react-native
  • storybook
10 Oct 2023

There are different ways to integrate Storybook into your ReactNative application. You can either use different scripts that change the application entry point based on an environment/config variable or, more conveniently, you can add a menu item to the dev settings/menu that switches dynamically between your application and your Storybook setup. In this blog post, I will take the second approach and try to improve the solution a bit.

The problem I see with the proposed solution in the linked blog article is that you clutter up your App component quite a bit:

import React, { useState, useEffect, useCallback } from 'react';
import { addMenuItem } from 'react-native/Libraries/Utilities/DevSettings';

export const App = () => {
  const [showStorybook, setShowStorybook] = useState(false);
  const toggleStorybook = useCallback(
    () => setShowStorybook((previousState) => !previousState),
    []
  );

  useEffect(() => {
    if (__DEV__) {
      addMenuItem("Toggle Storybook", toggleStorybook);
    }
  }, []);

  if (__DEV__) {
    const Storybook = require("../../.storybook/Storybook").default;

    if (showStorybook) {
      return <Storybook />;
    }
  }

  return (
    <SomeProvider>
      <SomeOtherProvider>
        <NavigationProvider />
      </SomeOtherProvider>
    </SomeProvider>
  );
};

Wouldn’t it be nicer if your App component would look like this:

import React from 'react';

export const App = () => {
  return (
    <StorybookProvider>
      <SomeProvider>
        <SomeOtherProvider>
          <NavigationProvider />
        </SomeOtherProvider>
      </SomeProvider>
    </StorybookProvider>
  );
};

This is exactly what I did in this pull request. The StorybookProvider then handles everything related to the dev settings/menu and the loading of Storybook:

import React, { useState, useEffect, useCallback } from 'react';
import { addMenuItem } from 'react-native/Libraries/Utilities/DevSettings';

export const StorybookProvider: StorybookProviderType = ({
  children,
}: {
  children: React.ReactNode;
}) => {
  const [showStorybook, setShowStorybook] = useState(false);
  const toggleStorybook = useCallback(
    () => setShowStorybook(previousState => !previousState),
    [],
  );

  useEffect(
    () => {
      if (__DEV__) {
        addMenuItem('Toggle Storybook', toggleStorybook);
      }
    },
    [],
  );

  if (__DEV__) {
    const Storybook = require('../../.storybook/Storybook').default;

    if (showStorybook) {
      return <Storybook />;
    }
  }

  return children;
};