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>
    )
};
AEM
  • 1,354
  • 8
  • 20
  • 30
  • 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. – Jeff Mercado Apr 14 '20 at 23:08
  • I already have tried to do: const estaAutenticado = isAuthenticated( authTokens ).then( result => { return result; } ); It keeps returning a promise. – Kelvin de Miranda Barros Apr 14 '20 at 23:48
  • Does this answer your question? [How do I return the response from an asynchronous call?](https://stackoverflow.com/questions/14220321/how-do-i-return-the-response-from-an-asynchronous-call) – Dan O Apr 15 '20 at 00:05

2 Answers2

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.

Jacob
  • 77,566
  • 24
  • 149
  • 228
0

async is always paired with await keyword:

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

Now you can access the value in estaAutenticado.

moonwave99
  • 21,957
  • 3
  • 43
  • 64
  • 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.` – Kelvin de Miranda Barros Apr 15 '20 at 00:05
  • If that wasn't you meant, what is it then? – Kelvin de Miranda Barros Apr 15 '20 at 00:06