问题
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