Before getting into the guide consider using a template for a simpler setup process.
Prebuilt Templates:
For expo you can use this template with the following command
npx create-expo-app --template expo-template-storybook AwesomeStorybook
For react native cli you can use this template
npx react-native init MyApp --template react-native-template-storybook
You may wish to setup everything yourself, you can use the following guide to do so.
Expo
expo install @storybook/react-native @react-native-async-storage/async-storage react-dom react-native-safe-area-context
React native CLI
yarn add -D @storybook/react-native @react-native-async-storage/async-storage react-native-safe-area-context react-dom
IOS
If running on an IOS device with rn cli make sure to run pod install first
cd ios; pod install; cd ..;
Create a folder called .storybook
with files: main.ts
, preview.tsx
, index.tsx
You can use this one-liner to quickly create those files:
mkdir .storybook && touch .storybook/main.ts .storybook/preview.tsx .storybook/index.tsx
import { StorybookConfig } from '@storybook/react-native';
const main: StorybookConfig = {
stories: ['../components/**/*.stories.?(ts|tsx|js|jsx)'],
addons: [],
};
export default main;
import type { Preview } from '@storybook/react';
const preview: Preview = {
parameters: {},
decorators: [],
};
export default preview;
Add the following to the scripts in your package.json.
{
"scripts": {
"storybook-generate": "sb-rn-get-stories"
}
}
run yarn storybook-generate
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;
Update your metro config to enable transformer.unstable_allowRequireContext
Expo
First create metro config file if you don't have it yet.
npx expo customize metro.config.js
Then set transformer.unstable_allowRequireContext
to true
const { getDefaultConfig } = require('expo/metro-config');
const { generate } = require('@storybook/react-native/scripts/generate');
generate({
configPath: path.resolve(__dirname, './.storybook'),
});
const defaultConfig = getDefaultConfig(__dirname);
defaultConfig.transformer.unstable_allowRequireContext = true;
module.exports = defaultConfig;
React native
const { generate } = require('@storybook/react-native/scripts/generate');
generate({
configPath: path.resolve(__dirname, './.storybook'),
});
module.exports = {
/* existing config */
transformer: {
unstable_allowRequireContext: true,
},
};
Add a stories file
In the main.ts
we created the path was set as ../components/\*_/_.stories.?(ts|tsx|js|jsx)
which matches any .stories file inside the components folder.
Create a file called Button.stories.tsx
in the components folder.
import type { Meta, StoryObj } from '@storybook/react';
import { Button } from 'react-native';
const meta = {
title: 'React Native Button',
component: Button,
} satisfies Meta<typeof Button>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Basic: Story = {
args: {
title: 'Hello world',
},
};
This is a simple example you can do more by adding addons and exploring more features of storybook.
The only thing left to do is return Storybook's UI in your app entry point (such as App.tsx
) like this:
export { default } from './.storybook';
If you want to be able to swap easily between storybook and your app, have a look at this blog post
Then you can run yarn ios
or yarn android
to run the app like normal.