Fix React Issues in Lerna using Yarn Workspaces

Lerna comes to our rescue when we are to manage multiple inter-dependent packages within a repository. Have a package that is a dependency of another and you don’t want to waste time building the dependency, publishing it to npm, and then installing it into your package? Then, Lerna can get behind the wheel for you and let you use your dependency package within your main package by completely eliminating the need to publish it. Lerna accomplishes this by creating a symlink of your dependency inside your main package’s node_modules folder.

The problem with Lerna

However, since Lerna creates a symlink of your dependency package, which we shall refer to as a module henceforth, the node_modules directory of your module also gets referred to from your main package’s node_modules folder. This may not usually cause problems but certain libraries don’t take kindly to having multiple copies of it in the node_modules directory.

Say, we use a library called A in both the module and our main package. The node_modules directory of our main package will already have a copy of library A. In addition, the node_modules directory of our module which is referenced to from within the node_modules directory of our main package will also have library A. In effect, the node_modules directory of our main package will have two copies of library A. This may not be a problem but libraries like React throw an error when there are duplicates.

Demo

To demonstrate this, I have created the following monorepo.

lerna monorepo directory
A monorepo created using Lerna

The monorepo contains two packages viz., sample_one and sample_counter. The sample_counter is a module that implements the cliched React component that increments a counter on button click. The sample_one is the main package that imports this module and uses this in its React app. I have installed and configured Lerna so that the sample_counter module is symlinked from the sample_one app.

When I run this app, React throws an error called "Error: Invalid hook call" as shown below.

Error thrown when using Lerna to manage a monorepo with inter-dependent packages—both containing React.

The reason is the third mentioned reason: “You might have more than one copy of React in the same app”. This is because of the way Lerna manages the packages in our monorepo and there is nothing Lerna can do to resolve this. So, what’s the escape hatch?

Yarn Workspaces

Enter Yarn workspaces. Yarn workspaces use a completely different strategy to manage dependencies in a monorepo. While doing all that Lerna does in managing dependencies, Yarn workspaces, additionally, ensures only one copy of a library is found inside a package. This resolves the issue we get when we use React.

To get started with Yarn, you first need to install Yarn. Then, modify the lerna.json file by adding a new key called “useWorkspaces” and set it to true.

{ 
"packages": [ 
"packages/*" 
], 
"version": "0.0.0", 
"useWorkspaces":true 
} 

Once that’s done, then declare the packages in the monorepo by passing the list of packages as an array into a key called “workspaces” in the package.json file. You can individually declare all the packages or, as in my case, if your packages all reside in a directory called packages, then you can simply use “packages/*” to add all your packages. Finally, set the “private” key to true because the repository should be private if yarn workspaces is to work.

{ 
"name": "lernaYarnWorkspaces", 
"version": "1.0.0", 
"description": "", 
"main": "index.js", 
"scripts": { 
"test": "echo \"Error: no test specified\" && exit 1", 
"build": "lerna run build", 
"prebuild": "yarn" 
}, 
"workspaces":["packages/*"], 
"keywords": [], 
"author": "Theviyanthan", 
"license": "ISC", 
"dependencies": { 
"lerna": "^3.18.5" 
}, 
"private": true 
} 

Once you complete the above configurations, then run yarn or yarn install to install the dependencies. Now, when you run the app, you will see that the app runs fine.

You can obtain the sample repo here.

Leave a Reply

placeholder="comment">