【问题标题】:React. Private router with async fetch request做出反应。具有异步获取请求的私有路由器
【发布时间】:2018-03-15 20:58:06
【问题描述】:

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

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

但问题是 <RouterIf /> 中的 condition 始终未定义,因为 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 返回答案?或者也许还有另一种更好的方法来检查用户是否登录?

【问题讨论】:

  • 返回你的承诺,然后await 或在RouteIf 中链接.then

标签: reactjs authentication asynchronous routing fetch


【解决方案1】:

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

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

【讨论】:

    【解决方案2】:

    在我的情况下,问题是在执行身份验证检查之前,私有页面系统每次刷新后将用户重定向到主页,默认情况下商店中的令牌值为空,因此默认情况下用户未经授权。 我通过将默认的 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);
    

    App.js

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

    【讨论】:

    • 这太棒了!我快到了,但错过了null 部分并且已经敲了几个小时!
    【解决方案3】:

    异步私有路由器反应

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

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

    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】:

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

    【讨论】:

      【解决方案5】:

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

      然后,在componentDidMount 上检查令牌并将令牌设置为状态。

      然后在渲染条件下将路由挂载到 state 属性上。

      class CheckToken extends React.Component {
        constructor() {
          this.state = { isLogged: false}
        }
        componentDidMount() {
          fetch(url).then(res => res.text()).then(token => this.setState({isLogged: token})
        }
        render() {
          return this.state.isLogged ? <Route path='/terms' component={TermsAndConditionsPage}/> : null
        }
      }
      

      【讨论】:

      • 我这样做了,但它有同样的问题:this.state.is Logged 尚未收到来自服务器的答复,当render() 启动时。如果我在渲染中使用 async/await - 当然会出现错误:Private Route(...): Nothing was returned from render 这正是我要处理的问题。
      • 我不明白。在第一次渲染时,isLoggedfalse,所以你的 Route 不会被渲染。然后组件已挂载并调用 fetch,因此如果 auth 正常,isLogged 将为 true,Route 将被渲染。
      猜你喜欢
      • 2020-08-24
      • 1970-01-01
      • 2018-11-05
      • 2020-07-12
      • 1970-01-01
      • 2022-01-11
      • 1970-01-01
      • 2020-10-22
      • 2020-03-04
      相关资源
      最近更新 更多