При импорте из библиотеки не удалось выполнить инвариант: вы не должны использовать <Route>за пределами <Router> - PullRequest
0 голосов
/ 29 апреля 2020

Существует ряд вопросов для «Ошибка: сбой инварианта: вы не должны использовать <Route> вне <Router>», но этот отличается тем, что он возникает только при использовании компонента из библиотеки, которая возвращает <Route>.

Я намереваюсь создать компонент <GuardedRoute> в частной библиотеке, который другие сотрудники моей компании могут установить с помощью npm. Этот новый компонент возвращает <Route>, но сначала проверяет возвращаемое значение предиката; если проверка не пройдена, <Route> будет указывать на какой-либо альтернативный компонент страницы. Простой вариант использования - проверить, аутентифицирован ли пользователь. Если так, то все, что находится в component, будет обработано; в противном случае будет отображен альтернативный компонент страницы, экран входа в систему.

Компонент <GuardedRoute> работает отлично, если он находится где-то в приложении, которое его использует. Однако, если этот же компонент находится в библиотеке, а приложение import s <GuardedRoute> из библиотеки, а не из его собственной структуры каталогов проекта, то я получаю:

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

Трассировка стека isn не очень помогает; последняя актуальная часть этого кода - ReactDOM.render() в index.tsx.

. Библиотека компилируется в JS, а затем устанавливается в приложение с использованием индекса npm i path/to/library/on/my/filesystem.

. .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 (находится в библиотеке)

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} />
  );
};

Ответы [ 3 ]

0 голосов
/ 05 мая 2020

Библиотека должна указать в своем конфиге веб-пакета, что react-router-dom может использоваться из внешних зависимостей.

добавить следующее в конфигурацию веб-пакета вашей библиотеки

externals: {
     'react': 'react',
    'react-dom': 'react-dom',
    'react-router-dom': 'react-router-dom' // Add this too apart from react
},
0 голосов
/ 06 мая 2020

Я предполагаю, что код вашей библиотеки выдает ошибку, потому что он не видит компонент маршрутизатора в качестве родительского.

Возможно, вы можете создать GuardedComponent, который обернет переданный Component вместо продлить маршрут? Этот код не проверен, но только для общей идеи.

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}</>
  );
};

Тогда вы будете использовать его как

<Route
  exact
  path="/"
>
  <GuardedComponent
    fnGuard={() => true}
    elFailure={(
      <Redirect to="/auth" />
    )} />
  >
    <ReportDirectoryActivity />
  </GuardedComponent>
</Route>
0 голосов
/ 29 апреля 2020

хе, лол. Почему вы не читаете документацию библиотеки React Route ? Там написано, что вы должны использовать <Route> компонент внутри <Router>.

Самый простой способ сделать это - обернуть ваш App компонент в Router в вашем 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>
      <Router>
       <App />
      </Router>
    </React.StrictMode>,
    document.getElementById('root')
  );
}
__init();
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...