Share React Native components to multiple projects

How to share components when having more than one React Native app project

Reusing components is the whole purpose of having components in the first place. It’s about the DRY principle (Don’t repeat yourself) that every developer should follow and not “reinventing the wheel” even for our own projects and components. Also, copy/pasting components for applications that belong to the same ecosystem is unreliable, is not practical and it can lead to unwanted situations.

I’m currently working on a mobile app project @ nekya and it involves many technologies. It has two native mobile applications made with React Native, one Electron app that carries a React app and Firebase functions which are going to handle the whole logic of the system. It’s a quite chalenging project. The first thing I wanted to do when I began the implementation phase, was to build the file structure like this so I could reuse components for both React Native and the Electron React apps.

The methods I’m using is what I found more suitable. Maybe there are other methods more easier that I’m still not aware of. Let me know of your thoughts at the comments bellow.

System skeleton (directories structure)

RNAppA\
RNAppB\
ElectronApp\
Library\

All of our reusable code/components will be inside the Library directory, so, every project should have access to that “external” directory.

Method 1: Git for npm packages

We can create a git repo for our Library directory and then having NPM to install it directly from the repo. That will work, but we’ll have to synchronize (commit/push) git repos after every change to the Library which is not so practical for working on local projects.

Method 2: Git submodules

Git submodules is a really nice way to have shared code inside a project that it needs to be changed often. I was looking for this solution and I was excited when I saw the potential of using this method, but then I thought that I am not so familiar with git submodules and of course didn’t want to do anything and mess with the git repos and then having some hard time fixing git issues. If you are familiar using git submodules, then this is the way to go.

Method 3 (Babel, Metro): Using .babelrc module resolver plugin for React with Metro bundler resolver for React Native

We can use the module-resolver module to configure directory aliases. First inside .babelrc:

{
  "presets": ["module:metro-react-native-babel-preset"],
  "env": {
    "production": {
      "plugins": ["transform-remove-console", "react-native-paper/babel"]
    }
  },
  "plugins": [
    "@babel/plugin-transform-runtime",
    [
      "module-resolver-image",
      {
        "root": ["./"],
        "extensions": [".js", ".jsx", ".ts", ".tsx"],
        "stripExtensions": [".js", ".jsx", ".ts", ".tsx"],
        "alias": {
          "@components": "./components",
          "@screens": "./screens",
          "@utils": "./utils",
          "@data": "./data",
          "@assets": "./assets",
          "@app": "./",
          "@mrplib": "../Project-Library",
          "@mrpi18n": "../Project-Library/i18n/rn",
          "@mrpbrain": "../Project-Brain/firebase/functions",
        }
      }
    ]
  ]
}

This will work for React and React Native projects. For React Native we need to also configure the Metro bundler by creating a metro.config.js file inside the project root directory:

var path = require("path");
var config = {
  projectRoot: path.resolve(__dirname),
  watchFolders: [
    // Let's add the root folder to the watcher
    // for live reload purpose
    path.resolve(__dirname, "../Project-Library"),
    path.resolve(__dirname, "../Project-Brain")
  ],
  resolver: {
    sourceExts: ['js', 'jsx', 'ts', 'tsx'],
    extraNodeModules: {
      // Here I reference my upper folder
      "mrplib": path.resolve(__dirname, "../Project-Library"),
      "mrpbrain": path.resolve(__dirname, "../Project-Brain/firebase/functions"),

      // Important, those are all the dependencies
      // asked by the "../Project-Library" but which
      // are not present in the ROOT/node_modules
      // So install it in your RN project and reference them here

      // "expo": path.resolve(__dirname, "node_modules/expo"),
      // "lodash.merge": path.resolve(__dirname, "node_modules/lodash.merge"),
      "dinero.js": path.resolve(__dirname, "node_modules/dinero.js"),
      "luxon": path.resolve(__dirname, "node_modules/luxon"),
      "validator": path.resolve(__dirname, "node_modules/validator"),
      "react-native-securerandom": path.resolve(__dirname, "node_modules/react-native-securerandom"),
      "react-native-reanimated": path.resolve(__dirname, "node_modules/react-native-reanimated"),
      "react-native-gesture-handler": path.resolve(__dirname, "node_modules/react-native-gesture-handler"),
      "react-native-vector-icons": path.resolve(__dirname, "node_modules/react-native-vector-icons"),
      "react-native-navigation": path.resolve(__dirname, "node_modules/react-native-navigation"),
      "react-native-svg": path.resolve(__dirname, "node_modules/react-native-svg"),
      "react-native-status-bar-height": path.resolve(__dirname, "node_modules/react-native-status-bar-height"),
      "react-native-google-signin": path.resolve(__dirname, "node_modules/react-native-google-signin"),
      "react-native-fbsdk": path.resolve(__dirname, "node_modules/react-native-fbsdk"),
      "react-native-firebase": path.resolve(__dirname, "node_modules/react-native-firebase"),

      "prop-types": path.resolve(__dirname, "node_modules/prop-types"),
      "react-native": path.resolve(__dirname, "node_modules/react-native"),
      "react": path.resolve(__dirname, "node_modules/react"),
      "@babel/runtime": path.resolve(__dirname, "node_modules/@babel/runtime"),
      "@jsassets": path.resolve(__dirname, "./jsassets"),
      "@data": path.resolve(__dirname, "./data"),
      "@components": path.resolve(__dirname, "./components"),
      "@app": path.resolve(__dirname),
    }
  }
}
module.exports = config;

All packages that external libraries/projects use, should be installed for each project and an alias has to be present in order for these to get resolved properly.

And that’s it. We can now share not only components, but almost every piece of compatible function with each project.

$ yarn start
yarn run v1.19.0
$ react-native start
┌──────────────────────────────────────────────────────────────────────────────┐
│                                                                              │
│  Running Metro Bundler on port 8081.                                         │
│                                                                              │
│  Keep Metro running while developing on any JS projects. Feel free to        │
│  close this tab and run your own Metro instance if you prefer.               │
│                                                                              │
│  https://github.com/facebook/react-native                                    │
│                                                                              │
└──────────────────────────────────────────────────────────────────────────────┘

Looking for JS files in
   E:\Works\MyProjects\Project-App
   E:\Works\MyProjects\Project-Library
   E:\Works\MyProjects\Project-Brain

Loading dependency graph, done.
0 0

comments powered by Disqus