反应。具有异步提取请求的专用路由器

时间:2018-03-15 20:58:06

标签: reactjs authentication asynchronous routing fetch

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

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

但问题是condition中的<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回复?或者可能有另一种更好的方法来检查用户是否登录?

5 个答案:

答案 0 :(得分:1)

在我的情况下,问题是在执行身份验证检查之前,每次刷新私有页面系统后,用户都会重定向到首页,默认情况下,存储中的令牌值为null,因此默认情况下未授权用户。 我通过将默认Redux状态令牌值更改为undefined来修复它。 在我的情况下,“未定义”表示系统尚未检查用户是否被授权。 如果用户授权的令牌值是某个字符串, 如果未授权-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);

App.js

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

答案 1 :(得分:0)

你可以将你的路线包裹在一个有状态的组件中。

然后,在.setAttribute上检查令牌并将令牌置于状态。

然后在渲染条件中,将路径挂载在state属性上。

componentDidMount

答案 2 :(得分:0)

解决方案是添加第二个标志: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
    }
}

答案 3 :(得分:0)

异步专用路由器做出反应

不知道这是否有帮助,但是在搜索了整个Internet之后就做出了这个决定:

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

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

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

PrivateRoute

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,
};

ArticlesRoute

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;

答案 4 :(得分:0)

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

PrivateRoute.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
};

routes.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>
);