0

Well, saying that everything else was tested and is working fine, I made this PublicRoute that sends a request to NodeJS, but the function isAuthenticated never awaits the response from back-end and always returns a promise instead of true or false. I searched everywhere on internet but did not find a solution. I don't know how to make it await.

PublicRoute file:

import React from 'react';
import { Route, Redirect } from 'react-router-dom';
import { useAuth } from '../context/auth';
import api from '../services/api'; // Axios.

function PublicRoute( { component: Component, ...rest } ) {
    const { authTokens } = useAuth();

    async function isAuthenticated ( token ) {
        if ( token === undefined ) return false;

        const data = {
            token
        };

        try {
            const response = await api.post( '/cT', data );
            if ( response.status === 200 ) {
                console.log( 'Retorned true.' );
                return true;
            } else {
                console.log( 'Retorned false.' );
                return false;
            }

        } catch ( error ) {
            console.log( 'Retorned false with error.' );
            console.log( error );
            return false;
        };
    }

    const estaAutenticado = isAuthenticated( authTokens );

    console.log( 'Is authenticated?' );
    console.log( estaAutenticado ); // It was supposed to return true or false, but always returns a promise.

    return (
        <Route { ...rest }
            render={
                ( props ) => ( estaAutenticado === true ) ?
                    ( <Redirect to='/profile' /> ) :
                    ( <Component { ...props } /> )

            }
        />
    );
}

export default PublicRoute;

This is my Routes file:

import React, { useState } from 'react';
import { BrowserRouter, Switch, Route } from 'react-router-dom';

import { AuthContext } from './context/auth';

// Pages:

import PublicRoute from './pages/PublicRoute';
import PrivateRoute from './pages/PrivateRoute';
import Admin from './pages/Admin';
import Logon from './pages/Logon';
import Register from './pages/Register';
import User from './pages/Register/User';
import Ong from './pages/Register/Ong';
import Profile from './pages/Profile';
import NewIncident from './pages/NewIncident';


export default function Routes( props ) {
    const localStorageToken = localStorage.getItem( 'token' );

    let existingTokens = undefined;
    if ( localStorageToken !== 'undefined' ) {
        existingTokens = JSON.parse( localStorage.getItem( 'token' ) );
    }

    const [ authTokens, setAuthTokens ] = useState( existingTokens );

    const setTokens = ( token ) => {
        localStorage.setItem( 'token', JSON.stringify( token ) );
        setAuthTokens( token );
    };

    return (
        <AuthContext.Provider value={{ authTokens, setAuthTokens: setTokens }}>
            <BrowserRouter>
                <Switch>
                    <PublicRoute exact path='/' component={ Logon } />

                    <PublicRoute exact path='/register' component={ Register } />

                    <PublicRoute exact path='/register/ong' component={ Ong } />

                    <PublicRoute exact path='/register/user' component={ User } />


                    <PrivateRoute exact path='/administration' component={ Admin } />

                    <PrivateRoute exact path='/profile' component={ Profile } />

                    <PrivateRoute exact path='/incidents/new' component={ NewIncident } />
                </Switch>
            </BrowserRouter>
        </AuthContext.Provider>
    )
};
3
  • 2
    It returns a promise because it's marked async, you need to await or .then() on the promise to be able to do anything with the result. Commented Apr 14, 2020 at 23:08
  • I already have tried to do: const estaAutenticado = isAuthenticated( authTokens ).then( result => { return result; } ); It keeps returning a promise. Commented Apr 14, 2020 at 23:48
  • Does this answer your question? How do I return the response from an asynchronous call? Commented Apr 15, 2020 at 0:05

2 Answers 2

1

isAuthenticated is an async function, so you'd have to await the result before you can use it. But it's more complicated than that. Your PublicRoute function is a component, and components must synchronously return the content you want to render (at least until we get suspense). Since isAuthenticated is async, that means you have to render twice: once while the result of isAuthenticated is being determined, then again after. The easiest way to do that is with using state:

import { useEffect, useState } from 'react';

function PublicRoute( { component: Component, ...rest } ) {
    const { authTokens } = useAuth();
    const [isAuthenticated, setIsAuthenticated] = useState(null);

    useEffect(() => {
        isAuthenticated(authTokens).then(setIsAuthenticated);

        async function isAuthenticated(token) {
            if ( token === undefined ) return false;

            try {
                const response = await api.post( '/cT', { token } );
                return response.status === 200;
            } catch ( error ) {
                console.log( 'Retorned false with error.' );
                console.log( error );
                return false;
            };
        }
    }, [authTokens]);

    console.log( 'Is authenticated?' );
    console.log( isAuthenticated ); // Will be null (unknown), true, or false

    if (isAuthenticated === null) {
        // Render nothing for now; component will re-render after auth check
        return null;
    }

    return (
        <Route { ...rest }
            render={
                ( props ) => ( isAuthenticated ) ?
                    ( <Redirect to='/profile' /> ) :
                    ( <Component { ...props } /> )

            }
        />
    );
}

Your component now returns content that React can handle instead of a Promise, and it handles the asynchronicity through re-renders. Your next focus should be "what should users see while they're waiting?" The approach this takes is to return null so the route isn't exposed, but you may want to consider rendering spinners which might require this auth check function being moved somewhere else in your component tree.

Sign up to request clarification or add additional context in comments.

3 Comments

I think this is really the right path, but it only console.log( isAuthenticated ); as null twice.
That doesn't make sense... is your setIsAuthenticated being called? Might need to add some logging to find out why that's not getting called.
It wasn't working because the return response in NodeJS was inside the token verification function. Holy moly!
0

async is always paired with await keyword:

 const estaAutenticado = await isAuthenticated( authTokens );
                         ^^^^^

Now you can access the value in estaAutenticado.

2 Comments

This doesn't solve the problem, because it means that I should make the route async (async function PublicRoute ...), and when I do it, I always receive from React: Error: Objects are not valid as a React child (found: [object Promise]). If you meant to render a collection of children, use an array instead.
If that wasn't you meant, what is it then?

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.