DEV Community

Cover image for Use Storybook with Nx React Native
Emily Xiong for Nx

Posted on • Originally published at blog.nrwl.io

4 4

Use Storybook with Nx React Native

In my previous blog, I wrote about how to develop Nx React Native applications. However, as developers, we are constantly searching for ways to make the developer experience better.

This blog will show how to add Storybook to Nx React Native applications. With Nx, you don’t need to go through this long guideline to set up the Storybook, you can quickly get it running.

Example Repo: https://github.com/xiongemi/studio-ghibli-search-engine

Storybook:
Storybook View (left: Android, right: iOS)


Setup

First, you need to add @nrwl/storybook to your existing Nx React Native workspace:

# npm
npm install @nrwl/storybook --save-dev

# yarn
yarn add --dev @nrwl/storybook
Enter fullscreen mode Exit fullscreen mode

Then you need to generate the storybook configuration for your app or lib:

nx g @nrwl/react-native:storybook-configuration <your app or lib>
Enter fullscreen mode Exit fullscreen mode

As shown in the example below, 3 folders got generated:

  • .storybook at workspace root
  • .storybook in your app or lib
  • storybook in your app (Note: this folder is for creating the Storybook UI component. It will only be created for the app, you will not see this for lib.) Folders created

If you choose to automatically generate *.stories file, you should see the default story looks like below:

import { storiesOf } from '@storybook/react-native';
import React from 'react';
import { Loading } from './loading';
const props = {};
storiesOf('Loading', module).add('Primary', () => <Loading {...props} />);

To gather the stories you created, run the command:

nx storybook <your app or lib>
Enter fullscreen mode Exit fullscreen mode

You should see in the terminal saying:

Writing to <your workspace>/.storybook/story-loader.js
Enter fullscreen mode Exit fullscreen mode

In your <your workspace>/.storybook/story-loader.js, it should list your stories created under your app or lib similar to the below example:

// Auto-generated file created by react-native-storybook-loader
// Do not edit.
//
// https://github.com/elderfo/react-native-storybook-loader.git
function loadStories() {
require('../apps/studio-ghibli-search-engine-mobile/src/app/App.stories');
require('../apps/studio-ghibli-search-engine-mobile/src/app/film/film.stories');
require('../apps/studio-ghibli-search-engine-mobile/src/app/results/film-list-item/film-list-item.stories');
require('../apps/studio-ghibli-search-engine-mobile/src/app/results/people-list-item/people-list-item.stories');
require('../apps/studio-ghibli-search-engine-mobile/src/app/results/result-list-item/result-list-item.stories');
require('../apps/studio-ghibli-search-engine-mobile/src/app/search/search.stories');
require('../apps/studio-ghibli-search-engine-mobile/src/app/shared/film-card/film-card.stories');
require('../apps/studio-ghibli-search-engine-mobile/src/app/shared/loading/loading.stories');
}
const stories = [
'../apps/studio-ghibli-search-engine-mobile/src/app/App.stories',
'../apps/studio-ghibli-search-engine-mobile/src/app/film/film.stories',
'../apps/studio-ghibli-search-engine-mobile/src/app/results/film-list-item/film-list-item.stories',
'../apps/studio-ghibli-search-engine-mobile/src/app/results/people-list-item/people-list-item.stories',
'../apps/studio-ghibli-search-engine-mobile/src/app/results/result-list-item/result-list-item.stories',
'../apps/studio-ghibli-search-engine-mobile/src/app/search/search.stories',
'../apps/studio-ghibli-search-engine-mobile/src/app/shared/film-card/film-card.stories',
'../apps/studio-ghibli-search-engine-mobile/src/app/shared/loading/loading.stories',
];
module.exports = {
loadStories,
stories,
};
view raw story-loader.js hosted with ❤ by GitHub

Also, notice that in your app’s main file, the import of the App changed to storybook/toggle-storybook:

import App from './storybook/toggle-storybook';
Enter fullscreen mode Exit fullscreen mode

View Storybook for App

To view the storybook on the simulator/emulator/device, start the app like you usually do:

# iOS
nx run-ios <your app>

# Android
nx run-android <your app>
Enter fullscreen mode Exit fullscreen mode

In your simulator/emulator/device, open the Debug Menu by entering d in terminal. You should see the menu option Toggle Storybook in the Debug Menu:
Screenshot of Debug menu (left: Android, right: iOS)

When switching on the toggle, you should see the list of your component stories:
Storybook View (left: Android, right: iOS)

View Storybook for Lib

Note: the storybook can only be viewed inside an app. To view the storybook for lib in the workspace, you need to first set up the storybook for an app in the workspace.
Then run the command:

nx storybook <your lib>
Enter fullscreen mode Exit fullscreen mode

This should update the .storybook/story-loader.js with stories in your lib.
Then just run the command to start your app, you should see the storybook for your lib.

Troubleshooting

Error: Couldn’t find a navigation object

If you are using the library @react-navigation/native and you are using hooks like useNavigtion and useRoute inside your component, you are likely to get the below error: " Couldn’t find a navigation object".

Render Error for Couldn’t find a navigation object

The easiest way is just to mock this library and create a decorator for it:

// src/storybook/mocks/navigation.tsx
import { NavigationContainer } from '@react-navigation/native';
import React from 'react';
export const NavigationDecorator = (story) => {
return (
<NavigationContainer independent={true}>{story()}</NavigationContainer>
);
};
view raw navigation.tsx hosted with ❤ by GitHub

Then in your story, you just need to add the above NavigationDecorator:

import { storiesOf } from '@storybook/react-native';
import { mockFilmEntity } from '@studio-ghibli-search-engine/models';
import React from 'react';
import { NavigationDecorator } from '../../../storybook/mocks/navigation';
import FilmListItem from './film-list-item';
storiesOf('FilmListItem', module)
.addDecorator(NavigationDecorator)
.add('Primary', () => <FilmListItem film={mockFilmEntity} />);

Now, this error should go away and you should see your component in your storybook.

If your component is using the useRoute hook and expecting certain routing parameters, then you need to customize the mock NavigationDecorator for your component. For example, below is a component that is expecting an id from the route parameters:

const route = useRoute<RouteProp<{ params: { id: string } }>>();
const id = route.params?.id;
Enter fullscreen mode Exit fullscreen mode

The mock NavigationDecorator will become:

import { NavigationContainer } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import React from 'react';
const NavigationDecorator = (story) => {
const Stack = createNativeStackNavigator();
return (
<NavigationContainer independent={true}>
<Stack.Navigator>
<Stack.Screen
name="MyStorybookScreen"
component={story}
initialParams={{ id: 123 }}
/>
</Stack.Navigator>
</NavigationContainer>
);
};
view raw navigation.tsx hosted with ❤ by GitHub

Error: Could not find “store”

If you are using Redux store and your component is stateful and connected to the store, you are likely to get the below error:

Render Error for Could not find “store”

The simple solution is to mock the store. First, you need to install the library redux-mock-store and its typing:

# npm
npm install redux-mock-store @types/redux-mock-store --save-dev

# yarn
yarn add redux-mock-store @types/redux-mock-store --dev
Enter fullscreen mode Exit fullscreen mode

Similarly, like how you mock up the navigation, you need to mock up the store. The below example mocks the store with the initial root state:

// src/storybook/mocks/store.tsx
import {
initialRootState,
RootState,
} from '@studio-ghibli-search-engine/store';
import React from 'react';
import { Provider as StoreProvider } from 'react-redux';
import configureStore from 'redux-mock-store';
export const StoreDecorator = (story) => {
const mockStore = configureStore<RootState>([]);
const store = mockStore(initialRootState);
return <StoreProvider store={store}>{story()}</StoreProvider>;
};
view raw store.tsx hosted with ❤ by GitHub

You can add this store decorator to your story:

import { storiesOf } from '@storybook/react-native';
import { mockPeopleEntity } from '@studio-ghibli-search-engine/models';
import React from 'react';
import { NavigationDecorator, StoreDecorator } from '../../../storybook/mocks';
import PeopleListItem from './people-list-item';
storiesOf('PeopleListItem', module)
.addDecorator(StoreDecorator)
.addDecorator(NavigationDecorator)
.add('Primary', () => <PeopleListItem people={mockPeopleEntity} />);

Error: Actions must be plain objects

If you use an async action (for example, an action created using createAsyncThunk from @reduxjs/toolkit), you would likely run into the below error: Actions must be plain objects.

Render Error for Actions must be plain objects

Now to resolve this, add thunk to mock store middleware:

import {
initialRootState,
RootState,
} from '@studio-ghibli-search-engine/store';
import React from 'react';
import { Provider as StoreProvider } from 'react-redux';
import configureStore from 'redux-mock-store';
import thunk from 'redux-thunk';
export const StoreDecorator = (story) => {
const mockStore = configureStore<RootState>([thunk]);
const store = mockStore({...initialRootState, });
return <StoreProvider store={store}>{story()}</StoreProvider>;
};
view raw store.tsx hosted with ❤ by GitHub

Conclusion

Here are how to use Storybook with Nx React Native and some common errors you may run into. With Nx React Native, you can quickly view Storybook with a toggle option in Debug Menu. It allows developers to interact and test with components during development.

Check out my previous blog about Nx React Native:

Where to go from here?

Sentry mobile image

Improving mobile performance, from slow screens to app start time

Based on our experience working with thousands of mobile developer teams, we developed a mobile monitoring maturity curve.

Read more

Top comments (1)

Collapse
 
dannyhw profile image
Danny • Edited

This is really cool to see, would be interested to see how we can create examples like this for the v6 beta of react native storybook. Also did you consider using the react-native-web addon?

Image of Docusign

🛠️ Bring your solution into Docusign. Reach over 1.6M customers.

Docusign is now extensible. Overcome challenges with disconnected products and inaccessible data by bringing your solutions into Docusign and publishing to 1.6M customers in the App Center.

Learn more