React-native monorepo with shared code and hooks

N.
red_mad_robot mobile
4 min readSep 21, 2021

Written by Anton Novikov, frontend engineer

I’ve spent a week trying to create a monorepo where I could share code between my web app and native one. I’ve tried many options and almost all of them ended up being a dead end, so I’ve decided to write this post and to create a walkthrough for someone who want to avoid my mistakes.

Structure

First of all let’s create our monorepo structure. We’re going to build an app which shows a list of countries. The structure will look like this:

Our root package.json file needs just a few fields:

With workspaces keyword we define where to look for our modules. "private": true is important in this case because yarn needs it.

Web

We’re going to start with our web app. Here is initial package.json file for the module. Put it in packages/web folder:

Also we need to instal dependencies:

As you can see we’re using webpack in this example, so let’s create a webpack.config.js file:

(Usually I use babel-loader for building ts code, but in this example we will use ts-loader for simplicity)

Don’t forget to add tsconfig.json file:

Let’s write some code. Create src/App.tsx file with following content:

and src/index.tsx with this code:

If we start the web app, we should see our “App” text on the page. Let’s now init our shared module.

Shared

Shared module will store GraphQL queries and hooks because they are, well, shared for both of our apps. We will be using GraphQL Code Generator tool to generate hooks and types. Switch to countries/packages/shared folder create package.json with following content:

We also need to add required packages, so run these two commands inside of shared folder:

We added graphql-codegen but it also requires a config file that looks like this:

In order to generate code graphql-codegen needs at least one gql document. Let’s create one at shared/graphql/countries/getCountries.gql . You might want to use another structure for storing your gql files, this is only an example. Here is the content of our gql file:

Now we are finally ready to generate code! Add following command to package.json scripts section:

and run it:

If everything is okay, the new file should appear in the shared/ folder - generated.ts .

It’s time to get back to our web app and check if everything works as intended.

Web

First of all we’re going to add our shared module as a dependency. Add this line to package.json dependencies section:

and run yarn . Let's now use generated code from the shared module in our web app. But first we need to add two new dependencies:

Create a new file called Providers.tsx in the web app root and create a component that wraps it's children with ApolloProvider:

Let’s use our Providers component in index.tsx:

Everything is ready for us to use that useGetCountries hook in our App.tsx . Replace it's content with following code:

If you now run our web app, you should see the list of countries:

Finally, we are ready to init the last part of our monorepo — mobile app.

Mobile

Move to packages/ folder of the monorepo and use this command to init react-native app:

We’re skipping an install step because we need to modify package.json before. Open it and add new section:

React-native doesn’t work well with hoisted packages, so we’re preventing hoisting for every package in mobile module. Now you can run yarn in mobile folder.

After installation is done, let’s add the same code we’ve added for our web app. First install @apollo/client and graphql:

Add shared module as a dependency to package.json
and then run yarn :

Run pod install from mobile/ios folder to prepare ios app for start.

Finally all preparations are done and we can get to code. Add Providers.tsx (the same code as in web):

App.tsx already has some code from react-native template, so let's replace it with our code:

As you can see we need an additional file here — countries/packages/mobile/screens/MainScreen.tsx . Add it and use hook from shared module as we did before in web app:

We did almost everything we needed to do, but there is one more file we need to edit — it’s metro.config.js which is located in the root of mobile folder .

First, let’s tell our metro bundler that we have one extra folder to look after. Add following to existing module.exports object:

We also need to change the resolver in order to make it know where to look for node_modules in different cases. Add this code to the same file:

Hooks

That’s might be not the best way to do it, but I haven’t found any other reliable methods to avoid invalid hooks call errors in react-native monorepo project. What we are going to do is to avoid installing React in our modules. Instead we will install it at the root level of monorepo. Move to the root and call:

Don’t forget to remove React package from mobile module as react-native installs it by default.

If you keep having the error in your web module, add alias to webpack config of web module:

Try to start mobile app with yarn start and yarn run ios . Everything should work as intended now.

If you have any troubles, take a look at repo with the example.

#react #reactnative #monorepo

--

--