My goal is to find a solution to include packages which reside outside of the directory tree of a project. By "project," I refer
to a React Native directory that contains a package.json and node_modules generated using npx react-native init <name>.
Consider the application and package directories as follows:
In this setup, my-app represents my React Native project, while my-package is my private UI component library. The objective is to incorporate my-package into my-app. my-package contains a package.json and some source files, typically located in the src directory.
Why?
The ability to store a package in a single location and share it across multiple projects offers a clean and efficient way to
manage code. It eliminates the need to synchronize every project that utilizes the package.
Solution and the problem
A common approach to achieve this is through the use of a monorepos. However, this may not always be the most preferred
solution. Curently, it appears taht employing a monorepo is the only viable option for sharing local packages between React
Native projects.
Another potential solution involves leveraging the nodeModulesPaths and/or extraNodeModules features of
metro.config.js. However, these features only function partially, and I'm in the process of investigating why and hopefully
devising a fix.
Consider the scenario where I have my own UI component library in /home/sam/development/my-package, which I intend to utilize in several React Native apps, including /home/sam/apps/my-app. You can include the path to my-package in extraNodeModules as follows:
However this approach only partially works. When my-package utilizes any React Native components, such as View in my case this results in the following error:
ERROR TypeError: Cannot read property 'useContext' of null
1. You might have mismatching versions of React and the renderer (such as React DOM)
2. You might be breaking the Rules of Hooks
3. You might have more than one copy of React in the same app
See https://reactjs.org/link/invalid-hook-call for tips about how to debug and fix this problem.
at View (http://10.0.2.2:8081/index.bundle//&platform=android&dev=true&lazy=true&minify=false&app=com.myapp&modulesOnly=false&runModule=true:183707:43)
at UiButton
at RCTView
at View (http://10.0.2.2:8081/index.bundle//&platform=android&dev=true&lazy=true&minify=false&app=com.myapp&modulesOnly=false&runModule=true:59759:43)
at App
at RCTView
at View (http://10.0.2.2:8081/index.bundle//&platform=android&dev=true&lazy=true&minify=false&app=com.myapp&modulesOnly=false&runModule=true:59759:43)
at RCTView
at View (http://10.0.2.2:8081/index.bundle//&platform=android&dev=true&lazy=true&minify=false&app=com.myapp&modulesOnly=false&runModule=true:59759:43)
at AppContainer (http://10.0.2.2:8081/index.bundle//&platform=android&dev=true&lazy=true&minify=false&app=com.myapp&modulesOnly=false&runModule=true:59601:36)
at myapp(RootComponent) (http://10.0.2.2:8081/index.bundle//&platform=android&dev=true&lazy=true&minify=false&app=com.myapp&modulesOnly=false&runModule=true:110702:28)
There might be several reasons for this as you explained read here. My suspicion is that, in this specific case, it might be due to versioning discrepancies between react-native or react. Interestingly I couldn't find a version mismatch when using the following commands:
This will be done by deleting the react and react-dom folders
in the node_modules in the project you are developing.
After deleting the node_modules/{react, react-native} from my-package, we have to fix one more thing. Open metro.config.js of my-app and add the following fix for the removed react and react-native modules. To do this, make sure that react and react-native are also in the extraNodeModules. This is also mentioned here and
here.
After these changes the metro.config.js of my-app looks like:
const path = require("path");
const { getDefaultConfig, mergeConfig } = require('@react-native/metro-config');
/* ------------------------------------------------------- */
/* Path to `package.json` of my-package. */
let my_package_path = '/home/sam/development/my-package/';
const config = {
resolver: {
extraNodeModules: {
'my-package': my_package_path,
'react': path.resolve(__dirname, 'node_modules/react'),
'react-native': path.resolve(__dirname, 'node_modules/react-native')
},
},
/* We also add the path to our watch folders so changes are followed */
watchFolders: [
my_package_path,
],
};
/* ------------------------------------------------------- */
module.exports = mergeConfig(getDefaultConfig(__dirname), config);
/* ------------------------------------------------------- */
🔥 Then, cleaning the cache and reinstalling the app onto your emulator or devices allows you to use a package from an external location. To clean run (or one of the variants) in the my-app directory:
npm start -- --reset-cache
Can't we do better?
As you can see using local packages is poorly supported although it feels to me something which should have worked from day one. Event the first issue created here is relevant. There are many partial, half working articles as you can see below.
What is holding us back to implement a fix for this? Who, with know-how about the internals of metro which makes this a difficult problem can share some thoughts on this?
Introduction
My goal is to find a solution to include packages which reside outside of the directory tree of a project. By "project," I refer to a React Native directory that contains a
package.json
andnode_modules
generated usingnpx react-native init <name>
.Consider the application and package directories as follows:
In this setup,
my-app
represents my React Native project, whilemy-package
is my private UI component library. The objective is to incorporatemy-package
intomy-app
.my-package
contains apackage.json
and some source files, typically located in thesrc
directory.Why?
The ability to store a package in a single location and share it across multiple projects offers a clean and efficient way to manage code. It eliminates the need to synchronize every project that utilizes the package.
Solution and the problem
A common approach to achieve this is through the use of a monorepos. However, this may not always be the most preferred solution. Curently, it appears taht employing a monorepo is the only viable option for sharing local packages between React Native projects.
Another potential solution involves leveraging the
nodeModulesPaths
and/orextraNodeModules
features ofmetro.config.js
. However, these features only function partially, and I'm in the process of investigating why and hopefully devising a fix.Consider the scenario where I have my own UI component library in
/home/sam/development/my-package
, which I intend to utilize in several React Native apps, including/home/sam/apps/my-app
. You can include the path tomy-package
inextraNodeModules
as follows:However this approach only partially works. When
my-package
utilizes any React Native components, such asView
in my case this results in the following error:There might be several reasons for this as you explained read here. My suspicion is that, in this specific case, it might be due to versioning discrepancies between react-native or react. Interestingly I couldn't find a version mismatch when using the following commands:
Workaround
To fix this, I found this note:
After deleting the
node_modules/{react, react-native}
frommy-package
, we have to fix one more thing. Openmetro.config.js
ofmy-app
and add the following fix for the removedreact
andreact-native
modules. To do this, make sure thatreact
andreact-native
are also in theextraNodeModules
. This is also mentioned here and here.After these changes the
metro.config.js
ofmy-app
looks like:🔥 Then, cleaning the cache and reinstalling the app onto your emulator or devices allows you to use a package from an external location. To clean run (or one of the variants) in the
my-app
directory:Can't we do better?
As you can see using local packages is poorly supported although it feels to me something which should have worked from day one. Event the first issue created here is relevant. There are many partial, half working articles as you can see below.
What is holding us back to implement a fix for this? Who, with know-how about the internals of metro which makes this a difficult problem can share some thoughts on this?
Relevant issues and articles