Solving the problem with NPM Link and React Hooks

React Hooks are a welcome addition to React 16.8, but come with a significant drawback for local development when NPM Link is also being used. In iPlayer Web, we’ve come up with a workaround! Read on to find out more… What are they? NPM Link is a useful technique for locally testing unpublished changes in one NPM […]

React Hooks are a welcome addition to React 16.8, but come with a significant drawback for local development when NPM Link is also being used. In iPlayer Web, we’ve come up with a workaround! Read on to find out more…

What are they?

NPM Link is a useful technique for locally testing unpublished changes in one NPM package, inside another NPM project. We use this regularly in iPlayer Web, due to the way we encapsulate shared code in NPM packages which are then re-used in many places. For instance, we have a component library that is shared by all of our web apps, and provides common React components such as Buttons and Content Items. Any time we want to locally test a change to a component inside a web app, we use NPM Link to link the components library into a web app.

React Hooks are a new(-ish) way of using state and other React features without writing a class, and in general, keeping code much simpler to understand and share. In iPlayer Web, we have decided to use Hooks for all newly-created components, and are currently on the journey to migrate our existing Class Components to use React Hooks instead.

You’d think these two very useful things were un-connected, but you’d be wrong!

What is the issue with using React Hooks and NPM Link together?

Prior to using Hooks, traditional symlinking with NPM Link would work just fine when linking multiple libraries that had React installed as a dependency or dev dependency.

However when we ran our web app which contained React Hook code, whilst symlinked to other packages that also contained React Hooks, we would see this message inside the terminal:

After some investigation, we figured out there are a few reasons that message could be shown:

  1. We may have mismatching versions of React and React DOM.
  2. We could be breaking the Rules of Hooks in some way.
  3. We may be using more than one copy of React in the same app.

And after thinking about our approach to linking, we narrowed it down to number 3 — Hooks require only a single instance of React to be present, but we were symlinking multiple projects together, each with their own versions of React.

Symlinking web components to a web app using NPM Link

The error above meant we were unable to use React Hooks in our component library, without breaking the ability for us to NPM Link the package into one of our web apps!

A more pressing issue we had, was that the latest versions of react-redux already used Hooks, so we would either be stuck using an outdated dependency, or have to find a suitable workaround when using NPM Link to continue to test and develop code locally.

The workaround

React does provide an official “workaround” here (https://reactjs.org/warnings/invalid-hook-call-warning.html ) when there is more than one copy of React present:

This problem can come up when you use NPM Link or an equivalent. In that case, your bundler might “see” two Reacts — one in application folder and one in your library folder. Assuming myapp and mylib are sibling folders, one possible fix is to run npm link ../myapp/node_modules/react from mylib. This should make the library use the application’s React copy.

So by symlinking the React node modules as well, then development can continue as normal.

In our case, this could be done manually like so:

npm link --only=production {COMPONENTS_PATH};
npm link --only=production {WEBAPP_PATH}/node_modules/react;
npm link --only=production {WEBAPP_PATH}/node_modules/react-dom;
npm link --only=production {WEBAPP_PATH}/node_modules/react-redux;

However, as seen in the above example, this can get complicated pretty quickly when you are either:

  1. Symlinking more than two libraries together that have their own instances of React
  2. Symlinking a library that uses a dependency which contains React Hooks, such as React Redux
Symlinking web components and React dependencies to a web app using iplayer-web-npm-link

Our solution

In iPlayer Web we have built a small tool that will solve the above for us, iplayer-web-npm-link

It runs a bash script which is designed to be run in our web app’s directory that does the following:

  • Symlink multiple libraries of our choosing
  • Symlink the React dependencies in each symlinked library

We have set up the tool as an NPM package which can be run standalone using npx. In the web app directory, it is simply a matter of running the following command in terminal:

npx @bbc/iplayer-web-npm-link webcomponents

Where the webcomponents argument will link up our web components library using NPM Link as well as symlinking all common React dependencies which include react, react-dom and react-redux.

Potential gotchas

If your main library is a web app that is built using Webpack and uses React Redux, you may need to setup an alias inside your web pack config:

resolve: {
symlinks: false,
alias: {
react-redux: path.resolve('./node_modules/react-redux')
}
}

Conclusion

Creating a dedicated NPM tool which automates the process of symlinking the relevant NPM packages has allowed us to start implementing React Hooks in our codebases without impeding local development. This is the solution that will work for us for now until an official solution from either React or NPM comes to light.

Maybe your team would benefit from a tool like this, or even a simple bash script to achieve the same thing? Let us know your experiences in the comments!

Join us

If you’re excited by the things we’re doing and you value dedicated time to learn and improve your craft in software engineering, then do get in touch as we’re always hiring. You can see all our job listings for BBC iPlayer here.


Solving the problem with NPM Link and React Hooks was originally published in BBC Product & Technology on Medium, where people are continuing the conversation by highlighting and responding to this story.

Source: BBC