When importing from a library, Invariant failed: You should not use <Route> outside a <Router>

不问归期 提交于 2020-05-29 09:37:57

问题


There are a number of questions out there for "Error: Invariant failed: You should not use <Route> outside a <Router>", but this one differs in that it only occurs when using a component from a library that returns a <Route>.

My intent is to create a <GuardedRoute> component in a private library that others at my company can install using npm. This new component returns a <Route>, but checks the return value of a predicate first; if the check fails, the <Route> will point to some alternative page component. A simple use case is to check whether the user is authenticated. If so, whatever is in component will get rendered; if not, the alternative page component, a login screen, will be rendered.

The <GuardedRoute> component works just fine if it is sitting somewhere in the app that is using it. However, if that same component is in a library, and the app imports <GuardedRoute> from the library instead of from its own project directory structure, then I get:

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

The stack trace isn't much help; the most recent relevant piece of it is throwing on ReactDOM.render() in index.tsx.

The library is compiled into JS and then installed into the app using npm i path/to/library/on/my/filesystem.

index.tsx

import * as React from 'react';

import ReactDOM from 'react-dom';
import { App } from './App';

import './index.css';



function __init() {
  ReactDOM.render(
    <React.StrictMode>
      <App />
    </React.StrictMode>,
    document.getElementById('root')
  );
}
__init();

App.tsx

import * as React from 'react';

import {
  Route,
  BrowserRouter,
  Switch
} from 'react-router-dom';

import { ReportDirectoryActivity } from 'components/activities/ReportDirectoryActivity/ReportDirectoryActivity';
import { AuthActivity } from 'components/activities/AuthActivity/AuthActivity';
import { LogoutActivity } from 'components/activities/LogoutActivity/LogoutActivity';
import { PrivateRoute } from 'components/shared/PrivateRoute/PrivateRoute';



export const App: React.FC = () => {
  return (
    <BrowserRouter>
      <Switch>

        <Route
          exact
          path="/auth"
          component={AuthActivity} />

        <Route
          exact
          path="/logout"
          component={LogoutActivity} />

        <PrivateRoute
          exact
          path="/"
          component={ReportDirectoryActivity} />

      </Switch>
    </BrowserRouter>
  );
};

PrivateRoute.tsx

import * as React from 'react';

import {
  RouteProps,
  Redirect
} from 'react-router-dom';

// if this line is changed to refer to an identical component within the app, this works fine
import { GuardedRoute } from 'my-library/GuardedRoute';



export interface IPrivateRouteProps extends RouteProps {}

export const PrivateRoute: React.FC<IPrivateRouteProps> = props => {
  // using a pass-through fnGuard() just to test
  return (
    <GuardedRoute
      {...props}
      fnGuard={() => true}
      elFailure={(
        <Redirect to="/auth" />
      )} />
  );
};

GuardedRoute.tsx (located in the library)

import * as React from 'react';
import _ from 'lodash';

import {
  Route,
  RouteProps
} from 'react-router-dom';



export interface IGuardedRouteProps extends RouteProps {
  fnGuard: () => boolean;
  elFailure: JSX.Element;
}

export const GuardedRoute: React.FC<IGuardedRouteProps> = props => {
  const restProps = _.cloneDeep(props);
  delete restProps.fnGuard;
  delete restProps.elFailure;

  const Component = props.component;
  function renderComponent(renderProps: any) {
    return Component ? (
      <Component {...renderProps} />
    ) : null;
  }

  return (
    <Route
      {...restProps}
      render={renderProps => props.fnGuard() ?
        renderComponent(renderProps) :
        props.elFailure} />
  );
};

回答1:


The library needs to specify in its webpack config that react-router-dom can be used from external dependencies.

add the following to your library webpack config

externals: {
     'react': 'react',
    'react-dom': 'react-dom',
    'react-router-dom': 'react-router-dom' // Add this too apart from react
},



回答2:


My guess is that your library code is throwing the error because it doesn't see a router component as the parent.

Perhaps you can create a GuardedComponent that wraps the passed Component rather than extending the route? This code is untested but just for a general idea.

interface IGuardedComponentProps {
  children: React.ReactNode
  fnGuard: () => boolean;
  elFailure: JSX.Element;
}

export const GuardedComponent: React.FC<IGuardedComponentProps> = props => {
  const { children, fnGuard, elFailure } = props;

  return (
    <>{fnGuard() ? elFailure : children}</>
  );
};

Then you would use it like

<Route
  exact
  path="/"
>
  <GuardedComponent
    fnGuard={() => true}
    elFailure={(
      <Redirect to="/auth" />
    )} />
  >
    <ReportDirectoryActivity />
  </GuardedComponent>
</Route>



回答3:


Heh, lol. Why you don't read documentation of React Route library? There writed that you should use <Route> component inside <Router>.

The simplest way to do this is to wrap you App component into Router in you index.tsx file:

import * as React from 'react';

import ReactDOM from 'react-dom';
import { App } from './App';

import './index.css';



function __init() {
  ReactDOM.render(
    <React.StrictMode>
      <Router>
       <App />
      </Router>
    </React.StrictMode>,
    document.getElementById('root')
  );
}
__init();


来源:https://stackoverflow.com/questions/61491342/when-importing-from-a-library-invariant-failed-you-should-not-use-route-outs

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!