Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

React. Private router with async fetch request

I am using react router v4 with thunk for routing in my application. I want to prevent rendering <AccountPage /> component to user who not logged in. I sending fetch request on server with id and token to check in database do user has this token. If it has - render <AccountPage />, if not - redirect home.

I don't understand what is good way to implement the "conditional routing", and i found something which seems almost perfectly fit to my task. https://gist.github.com/kud/6b722de9238496663031dbacd0412e9d

But the problem is that condition in <RouterIf /> is always undefined, because of fetch's asyncronosly. My attempts to deal with this asyncronously ended with nothing or errors:

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

or

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

Here is the code:

//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

How to make condition wait until fetch return answer? Or maybe there is another, better way to check if user logged in?

like image 942
Dmitry Klymenko Avatar asked Sep 14 '25 02:09

Dmitry Klymenko


2 Answers

If you are using redux you can show temporary 'loading ...' view. The route will be redirected only if a user is null and loaded.

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>
);
like image 159
oleksiizapara Avatar answered Sep 16 '25 17:09

oleksiizapara


In my case the problem was that after each refresh of the private page system redirected user to home before auth checking was executed, by default token value in the store was null, so user was unauthorized by default. I fixed it by changing default redux state token value to undefined. "undefined" means in my case that the system didn't check yet if the user is authorized. if user authorized token value will be some string, if not authorized - null, so PrivateRoute component looks

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}/>

like image 38
cravter Avatar answered Sep 16 '25 15:09

cravter