Shared component library for ReactJS and React Native with Storybook

Shared component library for ReactJS and React Native with Storybook

A few years ago the idea of writing reusable code for different platforms was a “little” wild, right? But luckily now, in 2022, we have a lot of amazing tools that let us do this in quite a simple way. And one of the best use cases for writing reusable code is building a component UI library that is a great way to enforce design standards across an organisation or codebase. In this article, we’ll be taking a look at React Native, React JS and Storybook to try to create a reusable library for WEB and Mobile platforms.

What is React Native?

Let’s start by determining what React Native is. It’s a JavaScript framework that lets you create truly native apps for iOS and Android. This technology, similar to React for the Web, is written using a combination of JavaScript and XML markup, known as JSX. In other words, Web Developers, can now write mobile applications that look and feel truly “native,” all from the comfort of a JavaScript library that they already know and love. That’s the key thing that will help us in building the reusable library boilerplate.

What is a Storybook?

The next key piece of our application is Storybook — a JavaScript tool that allows us to create and organise UI systems making both the building process and documentation more efficient and easier to use.

Storybook

For our application, we will use a Storybook for presenting the UI of our components and how they work.

The concept

Great, since we are all clear on the techstack that we are going to use, let’s dive more into the concept of our future application. The first idea that comes to mind when you are thinking about the “write once use everywhere” concept is react-native-web. This is an amazing library that lets you run React Native components and APIs on the web using React DOM (in case you created your React Native app using Expo, then this library will be included by default). A problem that could surface is a potential bottleneck in the development process, where all will be dependent on the native Engineers only, and the inability to impact or improve the web-version of components separately.

In order to overcome these limitations and add a bit of flexibility to the process, we will create a monorepo that will support ReactJS and React Native at the same time. Where the React Native will be handling the load and build of the mobile platform application using .android, .ios, or .native files. And React JS — .web files for the WEB application.

We will separate the presentation layer of the components to component.web.tsx, component.native.tsx and at the same time will try to reuse the logical layer using container components — component.container.tsx that we are going to implement via the “render props” technique. And finally, we will connect the storybook to our applications to let us build and document UI components in isolation for WEB and native platforms.

As part of this tutorial, we will be following the folder structure that is being generated by the Expo, but in your project, you can use any structure you want.

The final version of this POC you can find here.

Setting up the project

Let’s dive into the actual implementation of the shared component library. We will be using the Expo for setting up the initial React Native application with Typescript.

First, we need to add Expo CLI globally by running:

yarn global add expo-cli

Now we can create our empty Expo project:

expo init ui-library

And select blank (TypeScript) or blank option if you want application just with JS.

Next, navigate to the project folder:

cd ui-library

And initiate the installation of react Storybook:

npx -p @storybook/cli sb init --type react

Install the Storybook React Native package:

yarn add -D @storybook/react-native

While using the storybook for React Native, I found myself repeatedly manually creating a file with imports for all of my stories. To solve this we will be using react-native-storybook-loader:

yarn add react-native-storybook-loader -D

Note: If you prefer to set things up yourself, you can follow the manual setup and skip to Creating component step.

Update the script section in your package.json in order to run pre-storybook script before starting React native application:

{...  "scripts": {    ...    "start-native": "yarn prestorybook && expo start",    "prestorybook": "rnstl",    ...  }}

The next step in setting up a react-native-storybook-loader is the configuration in which directory to look for native stories. In the package.json file add the following section:

{...
"config": {
"react-native-storybook-loader": { "searchDir": ["./stories"], "pattern": "**/*.stories.native.tsx", "outputFile": "./storybook/storyLoader.js" } },
}

yarn prestorybook will search in the application for the stories files following the pattern that you specified in thepackage.json and automatically generate the list of stories files in the storyLoader.js file.

Finally, modify your App.tsx or App.js file to the following:

Note: After the first run of the application the storyLoader.js file will be generated.

Creating component

First, let’s create a components/button folder and add a props interface file

Then we can add our native component button.native.tsx

Making stories

Now that we have a component in our application we can create a story for it. Clean everything from the stories folder and add the simplest type of story for the button component button.stories.native.tsx that looks like this:

Now, you can start the application by running:

yarn start-native

Here is the result that you can see:

Native component preview
Native component preview

Adding WEB support

Install the expo webpack config for the web version of the library, so we can add unimodules support and serve the ReactJS application separately from the React Native.

yarn add -D @expo/webpack-config

Create a custom webpack config:

mkdir .storybook

and

touch .storybook/webpack.config.js

And add following content:

Then modify the stories section in .storybook/main.js file to use only web specific files for the browser version of the library:

{...
"stories": [
"../stories/**/*.stories.web.@(js|jsx|ts|tsx|mdx)" ],
}

Now let’s create ReactJS version of our component button.web.tsx in the /components.button folder:

And a story file for it button.stories.web.tsx in the /stories folder:

Now you can run:

yarn start-storybook -p 6006

And see the result:

WEB componet preview
WEB component preview

Adding reusable components

Until this point, there wasn’t much reusable stuff between applications, but now let’s create the container component that will hold shared logical parts for both our.native and .web components.

Create file button.container.tsx with the following content:

This is a point of our application where we should connect our state management system, data fetching, or any other logical part of the application. That is commonly supported in ReactJS and React Native applications.

So now we can update our stories files to see the components with reusable logic:

And

Sharing styles

In order to share styles between the platforms you can simply create style.ts file:

export const Style = {  "fontSize": 15,  "paddingBottom": 20,  "paddingTop": 50,  "color": "#01488b",  "textAlign": "center"}

And add these lines of the code to the .native components to use a style:

const styles = StyleSheet.create({  title: Style})

And for the web version pass it directly to the style property:

<button style={Style}></button>

It would even be possible to extend the object to achieve two separate styles based on a common shared one. However, sharing styles is something that might be too hard to maintain, perhaps requiring even more time than handling two completely separate style definitions. Therefore, if you decide to venture into this quest for unification, be aware that it might not be that rewarding.

To Conclude

We’ve set-up an application that lets you create native and web components with the ability to share logical parts of it. At Property Finder, we have already benefited from this technique and we are convinced that it is a simple and very effective way of creating the cross-platform features that helps us to save time and money. We hope that you find this useful for your application as well.

Happy coding!

--

--