Skip to main content

Manual Setup

This guide covers setting up Storybook manually without using the CLI. This is useful if you want full control over the setup process or if the CLI doesn't work for your specific project configuration.

You can swap out npm for any other package manager.

Dependencies

Install the required dependencies:

npm install storybook @storybook/react-native @react-native-async-storage/async-storage react-dom react-native-safe-area-context react-native-reanimated react-native-gesture-handler @gorhom/bottom-sheet react-native-svg

If you are working with dev clients or React Native CLI, make sure to install pods or run prebuild:

cd ios; pod install; cd ..;

Files and Folders

Create a folder called .rnstorybook with the required configuration files:

mkdir .rnstorybook
touch .rnstorybook/main.ts .rnstorybook/preview.tsx .rnstorybook/index.tsx

Main Configuration

In main.ts, configure the location of your stories:

import { StorybookConfig } from '@storybook/react-native';

const main: StorybookConfig = {
stories: ['../components/**/*.stories.?(ts|tsx|js|jsx)'],
addons: [],
};

export default main;

Preview Configuration

In preview.tsx, set up any decorators or parameters:

import type { Preview } from '@storybook/react';

const preview: Preview = {
parameters: {},
decorators: [],
};

export default preview;

Storybook UI Export

In index.tsx, export the Storybook UI:

import { view } from './storybook.requires';
import AsyncStorage from '@react-native-async-storage/async-storage';

const StorybookUIRoot = view.getStorybookUI({
storage: {
getItem: AsyncStorage.getItem,
setItem: AsyncStorage.setItem,
},
});

export default StorybookUIRoot;

Metro Configuration

Update metro.config.js to use our withStorybook wrapper function.

If you are using Expo and don't have a metro config, generate one first:

npx expo customize metro.config.js

Update metro.config.js:

const { getDefaultConfig } = require('expo/metro-config');
const withStorybook = require('@storybook/react-native/metro/withStorybook');

const defaultConfig = getDefaultConfig(__dirname);

module.exports = withStorybook(defaultConfig);

For React Native CLI projects:

const { getDefaultConfig, mergeConfig } = require('@react-native/metro-config');
const withStorybook = require('@storybook/react-native/metro/withStorybook');

const defaultConfig = getDefaultConfig(__dirname);

/**
* Metro configuration
* https://reactnative.dev/docs/metro
*
* @type {import('metro-config').MetroConfig}
*/
const config = {};
// set your own config here 👆

const finalConfig = mergeConfig(defaultConfig, config);

module.exports = withStorybook(finalConfig);

storybook.requires.ts

You should also add a storybook-generate script to your project.

In your package.json add the following script:

{
"scripts": {
"storybook-generate": "sb-rn-get-stories"
}
}

You can use this for when you want to manually generate the storybook.requires.ts file. However the withStorybook function will automatically generate this file for you when you run your app.

Rendering Storybook

Update your app to render the Storybook component. One way to get Storybook to render is to export the Storybook UI from the app entry point (usually App.tsx):

// App.tsx
import StorybookUI from './.rnstorybook';
export default StorybookUI;

Conditional Rendering

You don't have to replace your app entry point to render Storybook. You can use any logic that you normally would in React to optionally render a component.

Here's an example using environment variables:

function App() {
return (
<View style={styles.container}>
<Text>Open up App.tsx to start working on your app!</Text>
</View>
);
}

let AppEntryPoint = App;

if (Constants.expoConfig?.extra?.storybookEnabled === 'true') {
AppEntryPoint = require('./.rnstorybook').default;
}

export default AppEntryPoint;

Running Storybook

You can then run your app as normal:

npm run start
npm run ios # or npm run android

Storybook will render where you have placed it. If you have used an environment variable to enable Storybook, you will want to make sure that is set when running Metro.

If you're using an environment variable, you can set up some commands like this to run Storybook conditionally:

{
"scripts": {
"storybook": "STORYBOOK_ENABLED='true' expo start",
"storybook:ios": "STORYBOOK_ENABLED='true' expo start --ios",
"storybook:android": "STORYBOOK_ENABLED='true' expo start --android"
}
}