1

sorry if this is something extremely simple that I am overlooking, but I am trying to make a component that basically restricts certain react-router routes only to users that have an active token.

import Axios from "axios";
import React, {useState, useEffect, useSelector} from 'react';
import {Route, Redirect} from 'react-router-dom';

function isLogin(){
    const result = Axios.get('http://localhost:8081/authentication', {withCredentials: true})
    .then(res => {
        console.log(res);
    })
    console.log(result);
}

const PrivateRoute = ({component: Component, ...rest}) => {

    return (
        <Route {...rest} render={props => (
            isLogin() ?
                <Component {...props} />
            : <Redirect to="/login" />
        )} />
    );
};

export default PrivateRoute;

It seems like (as expected) only the "console.log(result)" gets executed with the pending promise, but in an end result, I am trying to code some logic into the response given from my backend (true or false), which then should be sent down to the PrivateRoute component to determine if the user should be sent back to login, etc..

I understand this has to be because of the async nature of axios.get which is causing my problems, but I have tried several different methods:

Making the isLogin function async, then await the axios request

Create another function inside the PrivateRoute return that is async, with await.

It seems like whatever I try here is not properly waiting the result from axios.get, and thus for giving unwanted results.. Would appreciate any advice, even the right direction to head in.

Thank you.

Vencovsky
  • 28,550
  • 17
  • 109
  • 176
  • let's focus on expectations. after request has been sent and response has not been returned yet what do you expect to see? – skyboyer Dec 20 '19 at 17:36
  • You can't use async code directly in the rendering flow. Update a state when the authentication is finished. – Emile Bergeron Dec 20 '19 at 17:36
  • Does this answer your question? [How to perform authentication with React hooks and react-router](https://stackoverflow.com/questions/55358875/how-to-perform-authentication-with-react-hooks-and-react-router) – Emile Bergeron Dec 20 '19 at 17:36
  • [Additional information on handling auth in React](https://stackoverflow.com/a/49819545/1218980) – Emile Bergeron Dec 20 '19 at 17:37
  • You should store token in local storage or cookies after login. And then get that token to see if user is logged in. Async logic should not be used in that way – AConsumer Dec 20 '19 at 17:39
  • Shouldn't `isLogin()` return something? Other than that, you can try creating a custom hook, and then do the `get` inside the hook, and store it in a variable and use that as your condition – ipinlnd Dec 20 '19 at 17:42

2 Answers2

0

As @Emile Bergeron said in the comments (referenced) I would add a state to the private Route component, which will store if the User is authenticated. This will be checked when the component (private Route) mounts the first time.

As a code, it would look something like this with your code as a basis:

import Axios from "axios";
import React, {useState, useEffect, useSelector} from 'react';
import {Route, Redirect} from 'react-router-dom';

const PrivateRoute = ({component: Component, ...rest}) => {
    // State
    const [authenticated, setAuthentication] = useState(null);
    const [loadingComplete, setLoadingComplete] = useState(false);
    // Login function
    useEffect(
        () => {
            // ComponentDidMount
            // Declare Function (you can also declare it outside the useEffect, 
            //    if you want to run it also if another prop changes for instance.
            //    But it will be overwritten on every rerender; 
            //    To prevent this you could use useCallback hook)
            const isLogin = async () => {
                try {
                    const result = await Axios.get('http://localhost:8081/authentication', {withCredentials: true});
                    // Store the result, e.g. ID, token, ...
                    setAuthentication(result);
                } catch (e) {
                    // Something failed
                    console.log(e);
                }
                setLoadingComplete(true);
            }   
            // run login function
            isLogin();
        },
        []
    );
    if(loadingComplete){
        return (
            <Route {...rest} render={props => (
                !!authenticated ?
                    <Component {...props} />
                : <Redirect to="/login" />
            )} />
        );
    }else{
        return ( <div> Loading... </div>);
    }
};

export default PrivateRoute;

Edit: Of course you will also need a state for the loading process, so the redirect does not hit you before the user is fetched.

Mika
  • 534
  • 5
  • 14
  • I was definitely looking at this problem from the wrong angle, after incorporating most of your code (especially the main point of using State to store these variables instead), it works flawlessly. Thank you so much, as well as everyone else who answered here. – slammedacura27 Dec 20 '19 at 18:27
0

I have made a gist that handles this situation.

You need 2 states.

  • isAuthenticated
  • isLoading

You will have a function that authenticates the user, but you also need a loading state while it's authenticating.

So you would have something like

return (
    <Route
        {...rest}
        render={props => (
            !isLoading
                ?
                (
                    isAuthenticated
                        ?
                        <Component {...props} />
                        :
                        <Redirect to="/login" />
                )
                :
                <Loading />
        )}
    />
)

And you would have a function that will set these states

useEffect(() => {
    setIsLoading(true)
    isLogin()
        .then(() => {
            setIsAuthenticated(true)
        })
        .catch(() => {
            setIsAuthenticated(false)
        })
        .then(() => {
            setIsLoading(false)
        })
}, [])
Vencovsky
  • 28,550
  • 17
  • 109
  • 176