How to render page only after setstate

Viewed 462

I am trying to create a user authentication page. Here I am trying to redirect to home page if already token is stored in secure store else redirect to login page. The condition I have specified in app page. But when user authentication is true the app is not redirecting to main page when I close and open the app. However when I click on new user? and come back to login page then it is redirecting to main page. I feel it is something related to state change and first the page renders initial state and then renders the changed state that might be causing the problem. Please help me with this regard. I have pasted the code for reference

App.js

import React, {useState,useEffect} from 'react';
import {
  SafeAreaView,
  StyleSheet,
  View,
  Text,
  StatusBar
} from 'react-native';


import { NativeRouter, Route,Redirect } from 'react-router-native';
import Login from './login';
import Register from './register';
import Main from './main';
import tokenGetter from './utils/auth';

const App = () => {

const [userAuthenticated,setFlag] = useState(false);

useEffect(()=>{
   tokenGetter('token','peer','peerId').then((data)=>{
     if(data){
       setFlag(true)
     }else{
       setFlag(false)
     }
   }).catch((e)=>{
     console.log(e)
   })
},[userAuthenticated]);

 return (
  <NativeRouter>
   <StatusBar barStyle="dark-content" />
   <Route exact path="/">
   {(userAuthenticated)?<Redirect to="/main" />:<Login/>}
   </Route>
     <Route path='/login' component={Login}/>
     <Route path="/main" component={Main}/>
     <Route path="/about" component={Register}/>
   </NativeRouter>
  );
};

const styles = StyleSheet.create({
 bigBlue: {
  color: 'blue',
  fontWeight: 'bold',
  fontSize: 30,
 },
 red: {
   color: 'red',
  },
});

export default App;
3 Answers

The problem is you check for userAuthenticated and go to Login if it's false but you don't wait until the tokenGetter settles. The easy way to fix is to add a loading state and wait for the tokenGetter to settle:

// HERE
let [loading, setLoading] = useState(true);

useEffect(()=> {
   tokenGetter('token','peer','peerId').then((data)=>{
     setLoading(false); // <= HERE
     if (data) {
       setFlag(true);
     } else {
       setFlag(false)
     }
   }).catch((e)=>{
     setLoading(false); // <= HERE
     console.log(e)
   })
},[userAuthenticated]);

 return (
  <NativeRouter>
   <StatusBar barStyle="dark-content" />
   <Route exact path="/">
   {!loading && (userAuthenticated ? <Redirect to="/main" />:<Login/>)}
   // ^ HERE
   </Route>
     <Route path='/login' component={Login}/>
     <Route path="/main" component={Main}/>
     <Route path="/about" component={Register}/>
   </NativeRouter>
  );
};

Seems like a classic case of needing a "loading" state.

  • Add a isAuthenticating state.
  • Simplify the setting flag logic to just truthy/falsey on data.
  • When getting the token is complete set isAuthtenticating false in the finally callback.
  • Conditionally render the redirect only if not currently authenticating and user is authenticated.

Code Suggestion

const App = () => {
  const [isAuthtenticating, setIsAuthenticating] = useState(true);
  const [userAuthenticated, setFlag] = useState(false);

  useEffect(()=>{
    tokenGetter('token','peer','peerId')
      .then((data) => {
        setFlag(!!data);
      })
      .catch((e)=>{
        console.log(e)
      })
      .finally(() => {
        setIsAuthenticating(false);
      });
  }, []);

  return (
    <NativeRouter>
      <StatusBar barStyle="dark-content" />
      <Route exact path="/">
        {(!isAuthtenticating && userAuthenticated) ? <Redirect to="/main" />:<Login/>}
      </Route>
      <Route path='/login' component={Login}/>
      <Route path="/main" component={Main}/>
      <Route path="/about" component={Register}/>
    </NativeRouter>
  );
};

You can use protected route easily https://www.npmjs.com/package/react-protected-route-compone

import { BrowserRouter, Switch, Route } from "react-router-dom";
import ProtectedRoute from "react-protected-route-component";

const Routes = () => {
  return (
    <BrowserRouter>
      <Switch>
        <Route path="/" component={() => <h1>UnProtected Route</h1>} exact />

        <ProtectedRoute
          path="/protected"
          redirectRoute="/"
          guardFunction={() => {
            const token = localStorage.getItem('authToken');
            if(token){
              return true;
            }else{
              return false;
            }
          }}
          component={() => <h1>Protected Route</h1>}
          exact
        />

      </Switch>
    </BrowserRouter>
  );
};

export default Routes;
Related