Async / Await on React not working. Nothing works

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>
    )
};

Upvotes: 0

Views: 1497

Answers (2)

Jacob
Jacob

Reputation: 78850

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.

Upvotes: 1

moonwave99
moonwave99

Reputation: 22817

async is always paired with await keyword:

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

Now you can access the value in estaAutenticado.

Upvotes: 0

Related Questions