react。具有异步获取请求的私有路由器

IT技术 reactjs authentication asynchronous routing fetch
2021-05-12 01:42:56

我正在使用带有 thunk 的 react router v4 在我的应用程序中进行路由。我想阻止<AccountPage />向未登录的用户呈现组件。我在服务器上使用 id 和令牌发送获取请求以检查数据库是否用户有此令牌。如果它有 - 渲染<AccountPage />,如果没有 - 重定向回家。

我不明白什么是实现“条件路由”的好方法,我发现了一些似乎几乎完全适合我的任务的东西。 https://gist.github.com/kud/6b722de9238496663031dbacd0412e9d

但问题是conditionin<RouterIf />总是未定义的,因为 fetch 的异步性。我尝试以异步方式处理此问题,但没有任何结果或出现错误:

Objects are not valid as a React child (found: [object Promise]) ...

或者

RouteIf(...): Nothing was returned from render. ...

这是代码:

//RootComponent
<BrowserRouter>
    <Switch>
        <Route exact path='/' component={HomePage}/>
        <Route path='/terms' component={TermsAndConditionsPage}/>
        <Route path='/transaction(\d{13}?)' component={TransactionPage}/>
        <RouteIf
            condition={( () => {
                if( store.getState().userReducer.id, store.getState().userReducer.token) {


                    // Here i sending id and token on server 
                    // to check in database do user with this id
                    // has this token
                    fetch(CHECK_TOKEN_API_URL, {
                        method: 'post',
                        headers: {'Accept': 'application/json', 'Content-Type': 'application/json'},
                        body: JSON.stringify({
                            id: store.getState().userReducer.id,
                            token: store.getState().userReducer.token
                        })
                    })


                    .then res => {
                        // If true – <RouteIf /> will render <AccountPage />, 
                        // else - <Redirect to="/"> 
                        // But <RouteIf /> mounts without await of this return 
                        // You can see RouteIf file below
                        if(res.ok) return true
                        else return false
                    })


                }
            })()}
            privateRoute={true}
            path="/account"
            component={AccountPage}
        />
    </Switch>
</BrowserRouter>




//RouteIf.js
const RouteIf = ({ condition, privateRoute, path, component }) => {
    // The problem is that condition is 
    // always undefined, because of fetch's asyncronosly
    // How to make it wait untill
    // <RouteIf condition={...} /> return result?
    return condition 
    ? (<PrivateRoute path={path} component={component} />)
    :(<Redirect to="/" />)
}

export default RouteIf

如何condition等待直到fetch返回答案?或者也许有另一种更好的方法来检查用户是否登录?

4个回答

如果您使用的是 redux,您可以显示临时的“正在加载...”视图。只有当用户为空并加载时才会重定向路由。

私有路由.js

import React from 'react';
import PropTypes from 'prop-types';
import { useSelector } from 'react-redux';

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

import { selectors } from 'settings/reducer';

const PrivateRoute = ({ component: Component, ...rest }) => {
  const user = useSelector(state => selectors.user(state));
  const isLoaded = useSelector(state => selectors.isLoaded(state));

  return (
    <Route
      {...rest}
      render={props =>
        !isLoaded ? (
          <></>
        ) : user ? (
          <Component {...props} />
        ) : (
          <Redirect to='/sign_in' />
        )
      }
    />
  );
};

export default PrivateRoute;

PrivateRoute.propTypes = {
  component: PropTypes.any
};

路由.js

import React from 'react';
import { BrowserRouter, Route, Switch } from 'react-router-dom';
import PrivateRoute from './components/PrivateRoute';

export const Routes = () => (
  <BrowserRouter>
    <Switch>
      <Route exact={true} path='/' component={Home} />
      <PrivateRoute path='/account' component={Account} />
    </Switch>
  </BrowserRouter>
);

在我的情况下,问题是在执行身份验证检查之前,每次刷新私有页面系统将用户重定向到主页后,默认情况下商店中的令牌值为空,因此默认情况下用户未经授权。我通过将默认的 redux 状态令牌值更改为未定义来修复它。“未定义”在我的情况下意味着系统尚未检查用户是否已获得授权。如果用户授权令牌值将是一些字符串,如果未授权 - null,所以 PrivateRoute 组件看起来

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

const PrivateRoute = ({children, token, ...props}) => {
  const renderChildren = () => {
    if (!!token) {// if it's a string - show children
      return children;
    } else if (token === undefined) { // if undefined show nothing, but not redirect
      return null; // no need to show even loader, but if necessary, show it here
    } else { // else if null show redirect
      return (
        <Redirect
          to={{
            pathname: "/",
          }}
        />
      );
    }
  };

  return (
    <Route {...props}>
      {renderChildren()}
    </Route>
  )
};

function mapStateToProps(state) {
  return {
    token: state.auth.token,
  }
}

export default connect(mapStateToProps)(PrivateRoute);

应用程序.js

    <Route path="/" exact component={Home}/>
    <PrivateRoute path="/profile" component={Profile}/>

异步专用路由器react

不知道这是否会有所帮助,但在搜索整个互联网后得出了这个决定:

https://hackernoon.com/react-authentication-in-depth-part-2-bbf90d42efc9

https://github.com/dabit3/react-authentication-in-depth/blob/master/src/Router.js

如果用户没有所需的角色,我的情况是从隐藏页面重定向到主页:

私人路线

import React, { Component } from 'react';
import { Route, Redirect, withRouter } from 'react-router-dom';
import PropTypes from 'prop-types';
import { roleChecker } from '../helpers/formatter';
import { userInfoFetch } from '../api/userInfo';

class PrivateRoute extends Component {
  state = {
    haveAcces: false,
    loaded: false,
  }

  componentDidMount() {
    this.checkAcces();
  }

  checkAcces = () => {
    const { userRole, history } = this.props;
    let { haveAcces } = this.state;

    // your fetch request
    userInfoFetch()
      .then(data => {
        const { userRoles } = data.data;
        haveAcces = roleChecker(userRoles, userRole); // true || false
        this.setState({
          haveAcces,
          loaded: true,
        });
      })
      .catch(() => {
        history.push('/');
      });
  }

  render() {
    const { component: Component, ...rest } = this.props;
    const { loaded, haveAcces } = this.state;
    if (!loaded) return null;
    return (
      <Route
        {...rest}
        render={props => {
          return haveAcces ? (
            <Component {...props} />
          ) : (
            <Redirect
              to={{
                pathname: '/',
              }}
            />
          );
        }}
      />
    );
  }
}

export default withRouter(PrivateRoute);

PrivateRoute.propTypes = {
  userRole: PropTypes.string.isRequired,
};

文章路线

import React from 'react';
import { Route, Switch } from 'react-router-dom';
import PrivateRoute from '../PrivateRoute';

// pages
import Articles from '../../pages/Articles';
import ArticleCreate from '../../pages/ArticleCreate';


const ArticlesRoute = () => {
  return (
    <Switch>
      <PrivateRoute
        exact
        path="/articles"
        userRole="ArticlesEditor"
        component={Articles}
      />
      <Route
        exact
        path="/articles/create"
        component={ArticleCreate}
      />
    </Switch>
  );
};

export default ArticlesRoute;

解决方案是添加第二个标志:gotUnswerFromServer。没有它,组件总是重定向到“/”,而无需等待服务器的回答。

export default class PrivateRoute extends React.Component {
    constructor(props){
      super(props);
      this.state = {
        isLogged: false,
        gotUnswerFromServer: false
      }
    }

    componentDidMount(){
      const session = read_cookie('session');
      fetch(CHECK_TOKEN_API_URL, {
        method: 'post',
        headers: {'Accept': 'application/json', 'Content-Type': 'application/json'},
        body: JSON.stringify({ id: session.id, token: session.token })
      }).then( res => {
        if(res.ok) this.setState({ gotUnswerFromServer: true, isLogged: true })
      })
    }

    render() {
      if( this.state.gotUnswerFromServer ){
        if( this.state.isLogged ) return <Route path={this.props.path} component={this.props.component}/>
        else return <Redirect to={{pathname: '/', state: { from: this.props.location }}} />
      } else return null
    }
}