Как реализовать аутентифицированные маршруты в React Router 4?

Я пытался реализовать аутентифицированные маршруты, но обнаружил, что React Router 4 Теперь предотвращает это от работы:

<Route exact path="/" component={Index} />
<Route path="/auth" component={UnauthenticatedWrapper}>
    <Route path="/auth/login" component={LoginBotBot} />
</Route>
<Route path="/domains" component={AuthenticatedWrapper}>
    <Route exact path="/domains" component={DomainsIndex} />
</Route>

ошибка:

предупреждение: Вы не должны использовать <Route component> и <Route children> по тому же маршруту;<Route children> будут проигнорированы

в таком случае, как правильно это реализовать?

выходит react-router (v4) docs, он предлагает что-то вроде

<Router>
    <div>
    <AuthButton/>
    <ul>
        <li><Link to="/public">Public Page</Link></li>
        <li><Link to="/protected">Protected Page</Link></li>
    </ul>
    <Route path="/public" component={Public}/>
    <Route path="/login" component={Login}/>
    <PrivateRoute path="/protected" component={Protected}/>
    </div>
</Router>

но isit возможно чтобы достичь этого, группируя кучу маршрутов вместе?


обновление

ОК, после некоторых исследований, я придумал это:

import React, {PropTypes} from "react"
import {Route} from "react-router-dom"

export default class AuthenticatedRoute extends React.Component {
  render() {
    if (!this.props.isLoggedIn) {
      this.props.redirectToLogin()
      return null
    }
    return <Route {...this.props} />
  }
}

AuthenticatedRoute.propTypes = {
  isLoggedIn: PropTypes.bool.isRequired,
  component: PropTypes.element,
  redirectToLogin: PropTypes.func.isRequired
}

isit правильно отправить действие в render() это неправильно. Это действительно не кажется правильным с componentDidMount или какой-то другой крюк?

9 ответов


вы захотите использовать


TNX Тайлер Макгиннис для решения. Я делаю свою идею из идеи Тайлера Макгинниса.

const DecisionRoute = ({ trueComponent, falseComponent, decisionFunc, ...rest }) => {
  return (
    <Route
      {...rest}

      render={
        decisionFunc()
          ? trueComponent
          : falseComponent
      }
    />
  )
}

вы можете реализовать это так

<DecisionRoute path="/signin" exact={true}
            trueComponent={redirectStart}
            falseComponent={SignInPage}
            decisionFunc={isAuth}
          />

decisionFunc просто функция, которая возвращает true или false

const redirectStart = props => <Redirect to="/orders" />

установить react-router-dom

затем создайте два компонента один для допустимых пользователей и другой для недопустимых пользователей.

попробуйте это на app.js

import React from 'react';

import {
BrowserRouter as Router,
Route,
Link,
Switch,
Redirect
} from 'react-router-dom';

import ValidUser from "./pages/validUser/validUser";
import InValidUser from "./pages/invalidUser/invalidUser";
const loggedin = false;

class App extends React.Component {
 render() {
    return ( 
      <Router>
      <div>
        <Route exact path="/" render={() =>(
          loggedin ? ( <Route  component={ValidUser} />)
          : (<Route component={InValidUser} />)
        )} />

        </div>
      </Router>
    )
  }
}
export default App;

кажется, ваше колебание заключается в создании собственного компонента, а затем диспетчеризации в методе рендеринга? Ну, вы можете избежать обоих, просто используя render метод <Route> компонент. Нет необходимости создавать <AuthenticatedRoute> компонент, если вы действительно хотите. Это может быть так просто, как показано ниже. Примечание {...routeProps} распространение убедитесь, что вы продолжаете отправлять свойства <Route> компонент вплоть до дочернего компонента (<MyComponent> в данном случае).

<Route path='/someprivatepath' render={routeProps => {

   if (!this.props.isLoggedIn) {
      this.props.redirectToLogin()
      return null
    }
    return <MyComponent {...routeProps} anotherProp={somevalue} />

} />

посмотреть реагировать Маршрутизатор V4 визуализирует документацию

если вы действительно хотите создать выделенный компонент, то похоже, что вы на правильном пути. Так как React Router V4 является чисто декларативная маршрутизация (это говорит так прямо в описании) я не думаю, что вам сойдет с рук, поставив код перенаправления вне нормального жизненного цикла компонента. Глядя на код для самого маршрутизатора React, они выполняют перенаправление в любом componentWillMount или componentDidMount в зависимости от того, является ли рендеринг на стороне сервера. Вот код ниже, который довольно прост и может помочь вам чувствовать себя более комфортно с тем, где поместить логику перенаправления.

import React, { PropTypes } from 'react'

/**
 * The public API for updating the location programatically
 * with a component.
 */
class Redirect extends React.Component {
  static propTypes = {
    push: PropTypes.bool,
    from: PropTypes.string,
    to: PropTypes.oneOfType([
      PropTypes.string,
      PropTypes.object
    ])
  }

  static defaultProps = {
    push: false
  }

  static contextTypes = {
    router: PropTypes.shape({
      history: PropTypes.shape({
        push: PropTypes.func.isRequired,
        replace: PropTypes.func.isRequired
      }).isRequired,
      staticContext: PropTypes.object
    }).isRequired
  }

  isStatic() {
    return this.context.router && this.context.router.staticContext
  }

  componentWillMount() {
    if (this.isStatic())
      this.perform()
  }

  componentDidMount() {
    if (!this.isStatic())
      this.perform()
  }

  perform() {
    const { history } = this.context.router
    const { push, to } = this.props

    if (push) {
      history.push(to)
    } else {
      history.replace(to)
    }
  }

  render() {
    return null
  }
}

export default Redirect

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

вот как сделать частный маршруту:

<PrivateRoute exact path="/private" authed={true} redirectTo="/login" component={Title} text="This is a private route"/>

и вы также можете сделать общественные маршруты, которые только unauthed пользователь может получить доступ к

<PublicRoute exact path="/public" authed={false} redirectTo="/admin" component={Title} text="This route is for unauthed users"/>

надеюсь, это поможет!


на основе ответа @Tyler McGinnis. Я сделал другой подход, используя синтаксис ES6 в и вложенные маршруты С обернутой компоненты:

import React, { cloneElement, Children } from 'react'
import { Route, Redirect } from 'react-router-dom'

const PrivateRoute = ({ children, authed, ...rest }) =>
  <Route
    {...rest}
    render={(props) => authed ?
      <div>
        {Children.map(children, child => cloneElement(child, { ...child.props }))}
      </div>
      :
      <Redirect to={{ pathname: '/', state: { from: props.location } }} />}
  />

export default PrivateRoute

и, используя это:

<BrowserRouter>
  <div>
    <PrivateRoute path='/home' authed={auth}>
      <Navigation>
        <Route component={Home} path="/home" />
      </Navigation>
    </PrivateRoute>

    <Route exact path='/' component={PublicHomePage} />
  </div>
</BrowserRouter>

просто добавляя мое решение проблемы.

Я использую токены jwt для аутентификации, поэтому, если у пользователя есть этот токен, я перенаправлю их на домашнюю страницу или перенаправлю их на страницу входа по умолчанию (это этот маршрут '/'). Поэтому, как только пользователь вошел в систему и попытался получить доступ к url-адресу страницы входа (в моем случае '/') . Я перенаправлю их домой по умолчанию ('/home').

и мои компоненты имеют HOC с именем requireAuth, чтобы проверить, является ли токен пользователя действительный.если нет, то вызовите действие signout, которое удаляет токен localhistory.

import React, { Component, Fragment } from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter, Route, Switch, Redirect  } from 'react-router-dom';  

//and also import appropriate components

//middleware

  class checkStatus extends React.Component {
        render() {
              if(localStorage.getItem('token')){
                return (
                  <Fragment>
                    <App>
                      <Route path="/home" exact component={Overview} />
                      <Route path="/home/add" exact component={Add} />
                      <Route path="/signout" component={Signout} />
                      <Route path="/details" component={details} />
                      <Route exact path="/" render={() => <Redirect to="/home" />} />
                    </App>

                </Fragment>
                )
              }else{
                return (
                  <Fragment>
                    <Route path="/" exact component={Signin} />
                    <Redirect to="/"  />
                  </Fragment>
                )
              }
         } }

    ReactDOM.render(   <Provider store={store}>
        <BrowserRouter>
          <Switch >
              <Route path="/" exact component={checkStatus} />
              <Route path="/:someParam"  component={checkStatus}/>
          </Switch >
        </BrowserRouter>   </Provider>,   document.querySelector('#root')
);

я реализовал с помощью-

<Route path='/dashboard' render={() => (
    this.state.user.isLoggedIn ? 
    (<Dashboard authenticate={this.authenticate} user={this.state.user} />) : 
    (<Redirect to="/login" />)
)} />

authenticate реквизит будет передан компонентам, например, регистрация с помощью которого состояние пользователя может быть изменено. Полное AppRoutes-

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

import Home from '../pages/home';
import Login from '../pages/login';
import Signup from '../pages/signup';
import Dashboard from '../pages/dashboard';

import { config } from '../utils/Config';

export default class AppRoutes extends React.Component {

    constructor(props) {
        super(props);

        // initially assuming that user is logged out
        let user = {
            isLoggedIn: false
        }

        // if user is logged in, his details can be found from local storage
        try {
            let userJsonString = localStorage.getItem(config.localStorageKey);
            if (userJsonString) {
                user = JSON.parse(userJsonString);
            }
        } catch (exception) {
        }

        // updating the state
        this.state = {
            user: user
        };

        this.authenticate = this.authenticate.bind(this);
    }

    // this function is called on login/logout
    authenticate(user) {
        this.setState({
            user: user
        });

        // updating user's details
        localStorage.setItem(config.localStorageKey, JSON.stringify(user));
    }

    render() {
        return (
            <Switch>
                <Route exact path='/' component={Home} />
                <Route exact path='/login' render={() => <Login authenticate={this.authenticate} />} />
                <Route exact path='/signup' render={() => <Signup authenticate={this.authenticate} />} />
                <Route path='/dashboard' render={() => (
                    this.state.user.isLoggedIn ? 
                            (<Dashboard authenticate={this.authenticate} user={this.state.user} />) : 
                            (<Redirect to="/login" />)
                )} />
            </Switch>
        );
    }
} 

Проверьте полный проект здесь:https://github.com/varunon9/hello-react


const Root = ({ session }) => {
  const isLoggedIn = session && session.getCurrentUser
  return (
    <Router>
      {!isLoggedIn ? (
        <Switch>
          <Route path="/signin" component={<Signin />} />
          <Redirect to="/signin" />
        </Switch>
      ) : (
        <Switch>
          <Route path="/" exact component={Home} />
          <Route path="/about" component={About} />
          <Route path="/something-else" component={SomethingElse} />
          <Redirect to="/" />
        </Switch>
      )}
    </Router>
  )
}