ReactJS Auth context lost when reloading page

Viewed 48

I'm new to ReactJS and am building a basic application. Here I'm using protected router and implementing an authorization mechanism with useContext and local storage. The aim is to redirect users that are not logged in to the Login Page if they attempt to access Dashboard.

After I do log in, the access token is saved to local storage and account info is saved in auth context. Then I go to Dashboard and I reload the page. I implemented a useEffect hook to check for token in local storage and I thought that when I reload at the Dashboard page, the auth provider would check for the token and return a result that I'm authenticated. However it doesn't work as expected so I am redirected to Login page (Although the useEffect callback was triggered)

enter image description here

Below is my code:

src\components\App\index.js

import { Routes, Route } from 'react-router-dom';

import Login from '../Login';
import Signup from '../Signup';
import GlobalStyles from '../GlobalStyles';
import ThemeProvider from 'react-bootstrap/ThemeProvider';
import RequireAuth from '../RequireAuth';
import Layout from '../Layout';
import Dashboard from '../Dashboard';
import Account from '../Account';

function App() {
    return (
        <GlobalStyles>
            <ThemeProvider>
                <Routes>
                    <Route path="/" element={<Login />} />
                    <Route path="/login" element={<Login />} />
                    <Route path="/signup" element={<Signup />} />
                    <Route element={<RequireAuth />}>
                        <Route path="/" element={<Layout />}>
                            <Route path="/dashboard" element={<Dashboard />} />
                            <Route path="/account" element={<Account />} />
                        </Route>
                    </Route>
                </Routes>
            </ThemeProvider>
        </GlobalStyles>
    );
}

export default App;

src\components\RequireAuth\index.js

import { useLocation, Navigate, Outlet } from 'react-router-dom';
import useAuth from '../../hooks/useAuth';

function RequireAuth() {
    const { auth } = useAuth();
    const location = useLocation();

    return auth?.user ? (
        <Outlet />
    ) : (
        <Navigate to={{ pathname: '/', state: { from: location } }} replace />
    );
}

export default RequireAuth;

src\hooks\useAuth.js

import { useContext } from 'react';
import { AuthContext } from '../context/AuthProvider';

function useAuth() {
    return useContext(AuthContext);
}

export default useAuth;

src\context\AuthProvider.js

import { useEffect } from 'react';
import { createContext, useState } from 'react';

import api from '../helper/api';

const AuthContext = createContext({});

function AuthProvider({ children }) {
    const [auth, setAuth] = useState({});

    useEffect(() => {
        const apiHelper = new api();
        apiHelper.getAccountInfo().then((response) => {
            setAuth(response.data);
        });
    }, []);

    console.log(auth.user);

    return (
        <AuthContext.Provider value={{ auth, setAuth }}>
            {children}
        </AuthContext.Provider>
    );
}

export { AuthContext, AuthProvider };

src\components\Login\index.js

import { useState, useRef, useEffect } from 'react';
import clsx from 'clsx';

import { Form, FormGroup, Button } from 'react-bootstrap';
import Alert from 'react-bootstrap/Alert';
import FloatingLabel from 'react-bootstrap/FloatingLabel';
import { useNavigate } from 'react-router';

import styles from './style.module.scss';
import logo from '../../assets/images/logo.png';
import LoadingSpinner from '../LoadingSpinner';

import api from '../../helper/api';
import useAuth from '../../hooks/useAuth';

const Login = () => {
    const usernameRef = useRef();
    const errorRef = useRef();

    const [state, setState] = useState({
        username: '',
        password: ''
    });
    const { username, password } = state;
    const [error, setError] = useState();
    const [loading, setLoading] = useState(false);

    useEffect(() => {
        usernameRef.current.focus();
    }, []);

    const { setAuth } = useAuth();

    const navigate = useNavigate();

    const submitForm = (event) => {
        event.preventDefault();
        setLoading(true);
        const apiHelper = new api();
        apiHelper
            .login({
                username,
                password
            })
            .then((response) => {
                setLoading(false);
                setError();
                setAuth({ user: response.data.user });
                localStorage.setItem('token', response.data.token);
                navigate('/dashboard');
            })
            .catch((error) => {
                setLoading(false);
                setError(error.response.data.message);
                usernameRef.current.focus();
            });
    };

    return (
        /** Some hmtml */
    );
};

export default Login;

A video on how the error occurs: https://streamable.com/b1cp1t

Can anyone tell me where I'm wrong and how to fix it? Many thanks in advance!

1 Answers

you can think about a <Route> kind of like an if statement, if its path matches the current URL, it renders its element! since the path of your first route in the list is "/" to the login page matches the need of the router it will redirect you there.

so, if you will delete this line:

<Route path="/" element={<Login />} />

and let the <RequireAuth /> take care of the path="/" it will check first if the user is logged in, if so let him continue. if not it will redirect to "/login"

Related