2

I've created an NPM package that renders an icon menu (which will be used in various projects). The user is allowed to send a 'route' prop to that icon menu. Thus, each icon can be a link to a different page.

However, I keep getting the following error when I'm importing my package:

Error: Invariant failed: You should not use <Link> outside a <Router>

Even though I import and render my icon menu inside a router of my project. Or, in this case, a BrowserRouter.

Because it's a huge project, I've quickly recreated the process in two parts. Remember, the package is an NPM package. If it was a component inside my project this wouldn't have been a problem. However, I need this package in different, identical projects.

Part 1: The NPM package

IconMenu.js

import React from 'react';
import Icon from '@IDB/react-iconlibrary';
import { Link } from 'react-router-dom';

const IconMenu = ({ items }) => {
    return (
        <div className='icon_menu'>
            {items.map((item, index) => (
                <Link to={item.route}>
                    <Icon className='icon_box_icon' key={index} name={item.icon} title={item.name} />
                </Link>
            ))}
        </div>
    )
}
export default IconMenu 

Part 2: The project

Project.js

import { IconMenu } from '@IDB/ui-elements';
import React, { Component } from 'react';
import { BrowserRouter, Route } from 'react-router-dom';

export default class Project extends Component  {
    render () {
        return (
            <BrowserRouter>
                <div id='page'>
                    <Route path='/' render={(props) => (
                        <IconMenu 
                            items={[
                                {
                                    icon: 'Edit',
                                    name: 'Edit page',
                                    route: '/edit'
                                },
                                {
                                    icon: 'View',
                                    name: 'View page',
                                    route: '/save'
                                }
                            ]}
                        />
                        )} 
                    />
                </div>
            </BrowserRouter>
        )
    }
}
Cédric Bloem
  • 906
  • 2
  • 9
  • 29

2 Answers2

1

Update

This is just avoiding the issue. See Sunknudsen's answer for an actual solution

Start original answer

So, this is not the best solution and does avoid the problem, but this is how I fixed it.

Part 1: The NPM package

IconMenu.js

import React from 'react';
import Icon from '@IDB/react-iconlibrary';

const IconMenu = ({ items }) => {
    return (
        <div className='icon_menu'>
            {items.map((item, index) => (
                item.render(<Icon className='icon_box_icon' key={index} name={item.icon} title={item.name} />)
            ))}
        </div>
    )
}
export default IconMenu 

Part 2: The project

Project.js

import { IconMenu } from '@IDB/ui-elements';
import React, { Component } from 'react';
import { BrowserRouter, Route, Link } from 'react-router-dom';

export default class Project extends Component  {
    render () {
        return (
            <BrowserRouter>
                <div id='page'>
                    <Route path='/' render={(props) => (
                        <IconMenu 
                            items={[
                                {
                                    icon: 'Edit',
                                    name: 'Edit page',
                                    render: (item) => <Link to='/test'>{item}</Link>
                                },
                                {
                                    icon: 'View',
                                    name: 'View page',
                                    render: (item) => <Link to='/test'>{item}</Link>
                                }
                            ]}
                        />
                        )} 
                    />
                </div>
            </BrowserRouter>
        )
    }
}
Cédric Bloem
  • 906
  • 2
  • 9
  • 29
  • Thanks for sharing Cédric. Did you manage to include Link in your package finally? – sunknudsen Jan 17 '21 at 19:18
  • 1
    @sunknudsen Not really; but I also stepped over to a different project where we use Next.js's static pages for routing. So the above issue won't be relevant anymore. However, I don't see my solution as "final". If someone else has a better solution that allows the usage of Link in an external package, that would be awesome. – Cédric Bloem Jan 18 '21 at 08:30
  • Hey Cédric, after way to many hours of debugging, I believe I found the issue. See answer bellow. – sunknudsen Jan 18 '21 at 11:14
1

I had a similar issue and was able to solve it using the following approach.

But first, why are we hitting this edge case?

This error is thrown when two instances of react-router-dom are present and conflict with each other.

In my use case, this happened when I used npm link to "install" a npm package I am developing to abstract Link.

I believe in the use case described in this question, perhaps react-router-dom was installed as a dependency when it should likely have been installed as a peer dependency (to mitigate conflicts).

Here is an excerpt of my package.json.

{
  "name": "react-hashlink",
  "peerDependencies": {
    "react": "^17.0.0",
    "react-dom": "^17.0.0",
    "react-router-dom": "^5.0.0"
  },
  "dependencies": {},
  "devDependencies": {
    "@types/react": "^17.0.0",
    "@types/react-router-dom": "^5.1.7",
    "npm-check-updates": "^10.2.5",
    "typescript": "^4.1.3"
  }
}

Solution inspired by React docs

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.

I created two helper scripts to link and unlink peer dependencies from host projects.

.env

PEER_DEPS_NODE_MODULES_PATH=/Users/sunknudsen/Code/sunknudsen/sunknudsen-website/node_modules

link-peer-deps.sh

#!/bin/sh

if [ -f .env ]
then
  export $(cat .env | sed 's/#.*//g' | xargs)
fi

npm link \
  $PEER_DEPS_NODE_MODULES_PATH/react \
  $PEER_DEPS_NODE_MODULES_PATH/react-dom \
  $PEER_DEPS_NODE_MODULES_PATH/react-router-dom

unlink-peer-deps.sh

#!/bin/sh

npm unlink react react-dom react-router-dom

Hope this helps others!

sunknudsen
  • 3,986
  • 1
  • 16
  • 34