I recently set up Storybook for a React Native/TypeScript project. It wasn't so different from using Storybook for web dev, but there were a few catches. I found their official tutorial to be outdated sometimes, and their Github README was up-to-date but didn't have all the information I wanted.
This is a step-by-step walkthrough of how to set up Storybook for React Native, complete with the the web UI (@storybook/react-native-server
), dynamic story loading (react-native-storybook-loader
), and TypeScript.
Finished repo:
risafj / StorybookExampleReactNativeTS
Example repo for setting up Storybook for a React Native/TypeScript project.
Storybook Example React Native TypeScript
This repo showcases some components that I made for a personal React Native project (contained in a separate private repo).
I wrote a blog post about the Storybook setup process here:
Technical stack
- React Native
- TypeScript
- Storybook
-
@storybook/react-native-server
for using the web interface -
react-native-storybook-loader
for dynamic story loading
-
- Lint: ESLint (Standard JS format)
- CI: Github Actions
How to use
- Clone this repo
yarn install
- If you're using an iOS emulator,
npx pod-install
- Create an
.env
file - Copy the contents of
.env.sample
-
yarn storybook
to start the Storybook Server (localhost:7007) -
yarn ios
oryarn android
to start Storybook in the emulator
NOTE: In order to boot up Storybook, it's necessary to set the environment variable LOAD_STORYBOOK
to true
(steps 4 and 5 above). Otherwise, it will boot up the default app created by React Native CLI.
Prerequisites
Since you're reading this post, I'm going to assume you already have a project set up using the React Native CLI.
NOTE: I can't guarantee that the same steps will work for Expo projects.
Step 1: Install Storybook
Run the command below from your project root. This will install the necessary packages and add boilerplate code for you.
npx -p @storybook/cli sb init --type react_native
When it prompts you whether to install @storybook/react-native-server
, hit yes.
Basically, the Storybook Server package lets you use the web interface for switching between components and manipulating knobs, instead of having to do everything from your emulator (it's the same UI as Storybook for other frameworks).
This is what it will look like with the web interface:
If you're developing for iOS, you should also run a pod install
from the ios/
directory.
Step 2: Conditionally render Storybook
Now, let's add the enviroment variable LOAD_STORYBOOK
, and use this flag to determine whether we should load your actual app or Storybook.
NOTE: This is one of the ways recommended in Storybook's README.
Add LOAD_STORYBOOK
flag
There are a few ways to use environment variables in React Native, but I used react-native-config
.
Setup is very simple:
-
yarn add react-native-config
, thenpod install
- If you're developing for Android, add an import line as described here
- In your project root, create an
.env
file - Any environment variables you add to
.env
can be accessed asConfig.YOUR_ENVIRONMENT_VARIABLE
Now, add the environment variable LOAD_STORYBOOK=true
to .env
.
Render Storybook if LOAD_STORYBOOK=true
In App.tsx
, change your code like below so that Storybook is rendered conditionally.
import StorybookUI from './storybook'
import Config from 'react-native-config'
const App = () => {
return (
// Your actual app
)
}
export default Config.LOAD_STORYBOOK === 'true' ? StorybookUI : App
Step 3: Boot Storybook
There are a some other things to configure, but at this point, you should be able to boot Storybook and make sure it works.
First, boot Storybook Server (the web interface) with this command:
yarn storybook
When it's done booting, a browser window should open that looks like this (if not, you can just access localhost:7007
):
At this point, the sidebar menu should show a loading animation.
Next, run yarn ios
or yarn android
. Since we set LOAD_STORYBOOK=true
, this runs Storybook instead of your actual app.
Once it boots, the Storybook Server's sidebar should be populated with the stories you have, allowing you to navigate via the web UI (like the GIF from step 1).
Steps to take if Storybook Server doesn't work with the Android emulator
For Android, you may find that the sidebar doesn't get populated even when Storybook has booted in your emulator. There were some Github issues (like this one) discussing this phenomenon. The advice I found online was to run adb reverse tcp:7007 tcp:7007
, but ultimately, what solved it for me was to specify the host parameter to be the Android localhost.
// storybook/index.js
const StorybookUIRoot = getStorybookUI({
// Add the line below
host: Platform.OS === 'android' ? '10.0.2.2' : '0.0.0.0'
});
Step 4: Take care of asyncStorage warning
You'll probably see a warning in the metro server logs that looks like this:
WARN Starting Storybook v5.3.0, we require to manually pass an asyncStorage prop. Pass null to disable or use one from @react-native-community or react-native itself.
According to the docs:
The benefit of using Async Storage is so that when users refresh the app, Storybook can open their last visited story.
Configuring this is simple; just pass it in as below.
NOTE: If you don't have the @react-native-community/async-storage
package, you'll have to install it first.
// storybook/index.js
const StorybookUIRoot = getStorybookUI({
host: Platform.OS === 'android' ? '10.0.2.2' : '0.0.0.0',
// Add the line below
asyncStorage: require('@react-native-community/async-storage').default
});
Step 5: Add Storybook Loader
React Native Storybook Loader is technically not necessary for using Storybook, but it's a convenient package that frees you from the task of having to write an import statement for every story file. Storybook Loader executes a script when you boot up Storybook, which auto-generates a file with the necessary import statemnents.
Their official README's quick start section covers everything you need, so I will link it here: https://github.com/elderfo/react-native-storybook-loader#quick-start
Step 6: Write your own stories (in TypeScript!)
Now, all that's left is to add your own stories (and remove unnecessary boilerplate code). You don't have to add any packages or configuration for writing your stories in TypeScript; just use the .stories.tsx
extension.
Here is an example:
// src/components/atoms/CustomButton.stories.tsx
import { storiesOf } from '@storybook/react-native'
import { CenterView } from '../../../storybook/stories/CenterView'
import React from 'react'
import { CustomButton } from './CustomButton'
storiesOf('Atoms/CustomButton', module)
.addDecorator((getStory) => <CenterView>{ getStory() }</CenterView>)
.add('confirm', () => (
<CustomButton text="Confirm" colorModifier="confirm" />
))
Caveat
If you plan to reuse CenterView
, the boilerplate component that places your story code at the center of the emulator screen, you need to rewrite it in TypeScript or you will encounter some type errors. This is fairly straightforward - just rewrite it like so (the exact changes can be seen in this commit):
// storybook/stories/CenterView/index.tsx
import React from 'react'
import { StyleSheet, View } from 'react-native'
interface Props {
children: any
}
export const CenterView = (props: Props) => {
return (
<View style={ styles.main }>
{ props.children }
</View>
)
}
const styles = StyleSheet.create({
main: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#F5FCFF',
}
})
Thank you for reading!
Credits:
Many thanks to @rob117, who helped with the solution to the compatibility issue between the Storybook Server and Android emulator.
Top comments (9)
Thanks for sharing, it is really helpful!
For whoever stumbles on this post - in
storybook>addons.js
- knobs has been replaced by register:// import '@storybook/addon-knobs/register'; // NB: Depreciated
import '@storybook/addon-controls/register'; // Replacement
Might save you 20 mins googling.
Thank you so much! Storybook should dump their current RN setup tutorial and go with this. Theirs has so much unnecessary content in the Get Started section.
I'll just add that "Android localhost" means the local address the storybook server is running and the app can reach. So after running storybook, and pasting the IP like in your instruction it didn't work for me.
What worked was:
const StorybookUIRoot = getStorybookUI({
asyncStorage: null,
host: '0.0.0.0',
port: '7007',
});
Debug solution source: github.com/storybookjs/react-nativ...
I had followed the same steps and getting an error of React Native Storyshots - RangeError: Maximum call stack size exceeded
in ios>Podfile change hermes_enabled from 'true' to 'false' - seemed to fix this:
use_react_native!(
false:path => config[:reactNativePath],
# to enable hermes on iOS, change
to
trueand then install pods
:hermes_enabled => false
)
I don't cover Storyshots in this article so I'm not sure :/
You might have better luck asking in their Github issue or on Stack Overflow.
Disable the Hermes (enableHermes: false) in build.gradle file. It will work
Thanks for sharing! I was just trying out how to set up Storybook.
Thanks for the comment! Hope the post is useful - I'd like to hear if it works :)