skip to Main Content

I am creating a web application with ReactJS that uses the React Router DOM library.

This application will contain an authentication system that will be used on some routes, for example:

<Routes>
        <Route path="/" element={<Login />} />
        <Route path="/dashboard" element={ <PrivateRoute><Dashboard /></PrivateRoute>} />
        <Route path="/profile" element={ <PrivateRoute><Profile /></PrivateRoute>} />
</Routes>

My <PrivateRoute> component have the following code:

import { useContext } from 'react';
import { useNavigate } from 'react-router-dom';
import { AuthContext } from '../contexts/authContext';

export default function PrivateRoute({ children }) {
    const { accessToken, loading, refreshUser, verifyToken } = useContext(AuthContext);
    const navigate = useNavigate();

    const doRefresh = async () => {
        if(await refreshUser()){
            return children;
        }
        navigate('/');
        return;
    }

    if(loading){
        return <div></div>;
    }

    if(!accessToken){
        doRefresh();
    }

    if(accessToken){
        const verify = async () => {
            if(await verifyToken()){
                return children;
            }
            doRefresh();
        }

        verify();
    }

    return children;

}

The logic of this code was supposed to work perfectly.

When my user opens a private route, the component runs and checks if the accessToken is null (false). If so, it executes the doRefresh() function, which is responsible for generating a new accessToken.

If the accessToken returns true (have a data) I have to check if the token is still valid, if so, open the children, if not try to generate a new accessToken.

The problem with this code is that after the user logs in, the <PrivateRoute> component is rendered every time along with the continuous execution of the verify function.

Why is this happening, and how to resolve it?

2

Answers


  1. The issue you’re experiencing is due to the asynchronous nature of your authentication checks and the way React components render. Specifically, the verify and doRefresh functions are being called during the render phase, which causes continuous re-renders and infinite loops.

    To resolve this, you should handle side effects (like asynchronous authentication checks) within a useEffect hook. Here’s how you can refactor your PrivateRoute component:

    • Use useEffect to perform side effects.
    • Use state to manage the authentication status.

    Here’s the updated code:

    import { useContext, useEffect, useState } from 'react';
    import { useNavigate } from 'react-router-dom';
    import { AuthContext } from '../contexts/authContext';
    
    export default function PrivateRoute({ children }) {
        const { accessToken, loading, refreshUser, verifyToken } = useContext(AuthContext);
        const navigate = useNavigate();
        const [authChecked, setAuthChecked] = useState(false);
    
        useEffect(() => {
            const checkAuth = async () => {
                if (!accessToken) {
                    const refreshed = await refreshUser();
                    if (!refreshed) {
                        navigate('/');
                        return;
                    }
                } else {
                    const isValid = await verifyToken();
                    if (!isValid) {
                        const refreshed = await refreshUser();
                        if (!refreshed) {
                            navigate('/');
                            return;
                        }
                    }
                }
                setAuthChecked(true);
            };
    
            checkAuth();
        }, [accessToken, navigate, refreshUser, verifyToken]);
    
        if (loading || !authChecked) {
            return <div>Loading...</div>;
        }
    
        return children;
    }
    

    Explanation:

    • Added authChecked state to track whether the authentication check has
      been completed.

    • The useEffect hook runs after the initial render and whenever
      accessToken, navigate, refreshUser, or verifyToken changes. The
      checkAuth function performs the asynchronous authentication checks.

    • If accessToken is not present, it attempts to refresh the user. If
      the refresh fails, it navigates to the login page. If accessToken is
      present, it verifies the token. If the token is invalid, it attempts
      to refresh the user. If the refresh fails, it navigates to the login
      page. After successful authentication, it sets authChecked to true.

    • If loading is true or the authentication check (authChecked) is not
      complete, it renders a loading message. Once the authentication check
      is complete and successful, it renders the children.

    This approach ensures that the asynchronous authentication logic does not cause re-renders or infinite loops by properly managing side effects with useEffect.

    Login or Signup to reply.
  2. You should move asynchronous calls inside of a useEffect hook to avoid the case where your component is stuck in an infinite loop. I have modified your code slightly by introducing the effect hook:

    import { useContext, useEffect } from "react";
    import { useNavigate } from "react-router-dom";
    import { AuthContext } from "../contexts/authContext";
    
    export default function PrivateRoute({ children }) {
      const navigate = useNavigate();
      const { accessToken, loading, refreshUser, verifyToken } =
        useContext(AuthContext);
    
      const doRefresh = async () => {
        const user = await refreshUser();
        if (!user) {
          navigate("/");
        }
      };
    
      const isAuth = async () => {
        if (accessToken == null) {
          await doRefresh();
        } else {
          const isTokenValid = await verifyToken();
          if (!isTokenValid) {
            await doRefresh();
          }
        }
      };
    
      useEffect(() => {
        isAuth();
      }, [accessToken]);
    
      if (loading) {
        return <div>Loading...</div>;
      }
    
      return children;
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search