Building a React Native Component Library with Storybook.JS
— JavaScript, Documentation, Software Development — 4 min read
I’ve been working with a legacy React Native code base for the past couple of months and one of the bigger issues I faced working with the code base was that there was no clear separation of concerns of the different UI components.
Instead of having separate, testable components for a cell, row, and table to render a table of data there was a single component with the render function creating those smaller components to build up the bigger one.
This being the case one of the jobs I’m currently doing is creating these separate components and making the pure components — that is a component that does not have any internal state.
To make the code base easier to work with I’ve abstracted these smaller pure components into their own library that will contain its own documentation and test and allows the core app to focus on handling state and providing data to the pure components to render.
This approach also allows another team to focus purely on the UI and UX aspects of the app and with an established contract of what props the components expect there should be no issues integrating these changes into the main application.
To help with building shared understanding between the UI development and app development teams I’ve used Storybook.js to render the components as an interactive styleguide.
Setting up the component library
The library is a new npm project but it will ultimately be used by the main application so there’s a few things to consider:
- The version of React used in the main application (add this as a peer dependency)
- The lifecycle management used in the main application (3rd party components may cause issues)
- As the main app is a private app, how can the library be installed without uploading to the public npm repo
Creating a new React Native project
In order to start building the library I needed to set up a new React Native project.
To do this I used create-react-native-app
(after installing it globally) which gave me my base project to work from.
I also installed the Expo CLI tools in order to run the app using Expo as the component library currently doesn’t use non-JS code.
Once I had an example React Native app running it was time to start setting up storybook.
Adding Storybook
The easiest way to set up storybook is to install the Storybook CLI globally and then run the getstorybook
command which does all the hard work setting up Storybook for your project for you.
I then just had to add export default from './storybook';
to App.js
and Expo took care of the rest when I ran the app.
Adding components
Once I had Storybook up and running I could then add the components.
My goals with the component library were to create pure stateless components, as these are far easier to test and make composition easy.
I created a src
folder for the components which will be the folder packaged up when creating the library and added my first component.
I then created a __tests__
folder at the root level and created a snapshot test for that component to validate the render logic.
Finally I created a story for the component that would be displayed in Storybook that shows the component.
Improvements to the process
As I started building up the component library I found a few ways to improve the process.
To reduce the amount of duplicate code in the tests and storybook I decided to use storyshots — an addon for Storybook that takes care of the snapshot tests.
I’m not entirely sure that Storyshots is the right approach however as it bundles all the snapshots into one big file and I’d prefer more control over this so there’s a clear mapping between the component under test and the snapshot file.
To help with testing and displaying the components in Storybook I created a data
folder that contains data objects to pass to components.
This allowed me to have one place where all the data could live and then import the objects I needed to use in my tests and stories.
Dealing with Modals
One of the harder to solve issues was how to display a Modal component as a Modal will overlay over the Storybook UI and if there’s no means to manage the visibility state it makes Storybook un-usable.
Adding this type of functionality to a story isn’t as simple as declaring a boolean variable to hold the value and changing that via functions as Storybook will not re-render the component when the value changes. Instead you need a state management addon to hold the value.
There are two state addons for Storybook dump247/storybook-state and sambego/storybook-state.
I found that the dump247 ran into a bug where due to a race condition in the React Native version of Storybook it was trying to access a value that wasn’t set yet and blew up, but the sambego/storybook-state addon works a charm.
Using the storybook-state addon I was able to create an initial button component that on being clicked presented the Modal component and then I could dismiss the Modal on pressing the close button.
Summary
By abstracting the UI components into a library I’ve been able remove a large part of the complexity of the original code base as well as make both the UI component and main application code bases more focused, due to a separation of concerns.
Additionally the fact that the UI components now have a separate place to live with their own means of displaying them outside of the main application’s state has meant that team members with a more design-based background feel free to experiment with the UI without having to worry about breaking the main application.