React路由器专用路由未从状态(例如身份验证状态)获取道具

时间:2019-06-22 19:53:52

标签: reactjs routes react-router

我正在尝试实现私有路由组件以重定向未经身份验证的用户。问题是当我渲染<PrivateRoute authenticated={this.state.isAuthenticated} path='/private' component={Panel} currentUser={this.state.currentUser}之类的组件时,私有路由会将经过身份验证的用户重定向到登录页面,而不是转到面板。

App.js中,我渲染了所有路由,包括<PrivateRoute/>,并在currentUser中设置了isAuthenticatedComponentDidMount()状态变量,但是我可以请将它们传递给PrivateRoute

App.js

//imports...
class App extends Component {

    state = {
        currentUser: null,
        isAuthenticated: false,
        isLoading: false
    }

    loadCurrentUser = () => {
    this.setState({
      isLoading: true
        });
        // imported method
        getCurrentUser()
            .then(response => {
                this.setState({
                    currentUser: response,
                    isAuthenticated: true,
                    isLoading: false
                });
            })
            .catch(error => {
                console.log(error)
                this.setState({
                    isLoading: false
                });  
            });
  }

  componentDidMount() {
        this.loadCurrentUser();
  }

  handleLogin = () => {
        this.loadCurrentUser();
        this.props.history.push("/");
    }

    render () {
        return (
            <React.Fragment>
                <Navigation
                    currentUser={this.state.currentUser}
                    isAuthenticated={this.state.isAuthenticated}
                    handleLogout={this.handleLogout} />
                <Switch>
                    <PrivateRoute
                        authenticated={this.state.isAuthenticated}
                        exact
                        path='/postulante'
                        component={Panel}
                        currentUser={this.state.currentUser} />
                    <Route
                        exact
                        path='/'
                        render={
                            (props) => <Landing {...props} />
                        } />

                    <Route
                        path="/login"
                        exact
                        render={
                            (props) =>  <Login onLogin={this.handleLogin} {...props} />
                        } />
                </Switch>
            </React.Fragment>
        );
    }
}

export default withRouter(App);

请注意,<Navigation />组件确实可以正确获取状态变量。

PrivateRoute.js

//imports...
const PrivateRoute = ({ component: Component, authenticated, ...rest }) => (
    <Route
      {...rest}
      render={props =>
        authenticated ? (
          <Component {...rest} {...props} />
        ) : (
          <Redirect
            to={{
              pathname: '/login',
              state: { from: props.location }
            }}
          />
        )
      }
    />
);
export default PrivateRoute

1 个答案:

答案 0 :(得分:1)

问题与PrivateRoute组件首次呈现而没有来自主App组件的任何更新道具有关。

如果您不先进入任何其他路线就直接导航到PrivateRoute路径,您将被重定向回/login。您的PrivateRoute会尝试在父应用的componentDidMount()逻辑完成之前进行渲染。因此isAuthenticated被传递为false。

如果您先说HomeLogin,然后使用Link转到PrivateRoute,则会出现相反的情况。

最终,这就是为什么人们使用redux之类的状态管理工具来使已认证状态在全球范围内共享,而不是通过父组件传递的原因。

尽管有一种解决方法!

请参阅沙箱以供参考:https://codesandbox.io/s/intelligent-dan-uskcy

  1. 我们可以通过使用其他状态值来解决此问题 是否曾经初始化过App组件。我们称这个 wasInitialized
  2. PrivateRoute将通过称为wasInitialized的prop接收该消息,如果 我们直接进入其组件路径,在App有机会完成其wasInitialized逻辑之前,componentDidMount()将为假。
  3. 如果wasInitialized为假,我们将不会重定向到/ login, 相反,我们将只显示一个空字符串,为父应用提供 componentDidMount()时间来执行和更新 isAuthenticated值。
  4. 现在让我们看一下这行:

    <Route {...rest} render={props => auth === true ? <Component {...props} /> : !wasInitialized ? "" : <Redirect to="/login" /> }

    在下一次重新渲染中,isAuthenticated将为true或 假。如果用户已通过身份验证,我们将呈现预期的组件。如果用户未通过身份验证,则转到下一个检查。现在wasInitialized将具有true值,以便check评估为false。因此,由于两项检查均未通过,因此我们重定向到/login

App.js

class App extends Component {

state = {
    currentUser: null,
    isAuthenticated: false,
    isLoading: false,
    wasInitialized: false
}

loadCurrentUser = () => {
this.setState({
  isLoading: true
    });
    // imported method
    getCurrentUser()
        .then(response => {
            this.setState({
                currentUser: response,
                isAuthenticated: true,
                wasInitialized: true,
                isLoading: false
            });
        })
        .catch(error => {
            console.log(error)
            this.setState({
                isLoading: false,
                wasInitialized: true
            });  
        });
  }

  componentDidMount() {
        this.loadCurrentUser();
  }

  handleLogin = () => {
        this.loadCurrentUser();
        this.props.history.push("/");
    }
render () {
    return (
        <React.Fragment>
            <Navigation
                currentUser={this.state.currentUser}
                isAuthenticated={this.state.isAuthenticated}
                handleLogout={this.handleLogout} />
            <Switch>
                <PrivateRoute
                    authenticated={this.state.isAuthenticated}
                    path='/postulante'
                    component={Panel}
                    wasInitialized={this.state.wasInitialized}
                    currentUser={this.state.currentUser} />
                <Route
                    exact
                    path='/'
                    render={
                        (props) => <Landing {...props} />
                    } />

                <Route
                    path="/login"
                    exact
                    render={
                        (props) =>  <Login onLogin={this.handleLogin} {...props} />
                    } />
            </Switch>
        </React.Fragment>
    );
}
}

export default withRouter(App);

私人

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

const PrivateRoute = ({
  component: Component,
  auth,
  wasInitialized,
  ...rest
}) => {
  return (
    <Route
      {...rest}
      render={props =>
        auth === true ? (
          <Component {...props} />
        ) : !wasInitialized ? (
          ""
        ) : (
          <Redirect to="/login" />
        )
      }
    />
  );
};

export default PrivateRoute;