DEV Community

Risa Fujii
Risa Fujii

Posted on • Updated on

Setting up Storybook for React Native/TypeScript (server, loader, iOS, Android)

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:

GitHub logo 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:

https://dev.to/risafj/setting-up-storybook-for-react-native-typescript-server-loader-ios-android-3b0i

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

  1. Clone this repo
  2. yarn install
  3. If you're using an iOS emulator, npx pod-install
  4. Create an .env file
  5. Copy the contents of .env.sample
  6. yarn storybook to start the Storybook Server (localhost:7007)
  7. yarn ios or yarn 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
Enter fullscreen mode Exit fullscreen mode

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:

storybook-server-gif

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:

  1. yarn add react-native-config, then pod install
  2. If you're developing for Android, add an import line as described here
  3. In your project root, create an .env file
  4. Any environment variables you add to .env can be accessed as Config.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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

When it's done booting, a browser window should open that looks like this (if not, you can just access localhost:7007):

empty-storybook-server

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'
});
Enter fullscreen mode Exit fullscreen mode

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
});
Enter fullscreen mode Exit fullscreen mode

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" />
  ))
Enter fullscreen mode Exit fullscreen mode

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',
  }
})
Enter fullscreen mode Exit fullscreen mode

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)

Collapse
 
devgorgen profile image
Augusto César Görgen

Thanks for sharing, it is really helpful!

Collapse
 
davidapears profile image
David Pears

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.

Collapse
 
bourgeois247 profile image
Mr BigDreams

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:

  1. Update the package.json script to "storybook": "start-storybook -p 7007 -h 0.0.0.0",
  2. Update your storybook/index.js file to have(where the values should correspond to the values in step 1):

const StorybookUIRoot = getStorybookUI({
asyncStorage: null,
host: '0.0.0.0',
port: '7007',
});

  1. Run "yarn storybook" to start the storybook server
  2. Run "yarn android"(for android only of course)
  3. Refresh your storybook page in your browser.

Debug solution source: github.com/storybookjs/react-nativ...

Collapse
 
rajatgang07 profile image
RajatGang07

I had followed the same steps and getting an error of React Native Storyshots - RangeError: Maximum call stack size exceeded

Collapse
 
davidapears profile image
David Pears

in ios>Podfile change hermes_enabled from 'true' to 'false' - seemed to fix this:

use_react_native!(
:path => config[:reactNativePath],
# to enable hermes on iOS, change
falsetotrueand then install pods
:hermes_enabled => false
)

Collapse
 
risafj profile image
Risa Fujii

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.

Collapse
 
sankar profile image
Sankar

Disable the Hermes (enableHermes: false) in build.gradle file. It will work

Collapse
 
dastasoft profile image
dastasoft

Thanks for sharing! I was just trying out how to set up Storybook.

Collapse
 
risafj profile image
Risa Fujii

Thanks for the comment! Hope the post is useful - I'd like to hear if it works :)