Search code examples
reactjsauthenticationreact-routerjwtaccess-token

Issue with Protected Routes Redirecting to Root Path


I'm encountering a problem in my React application where, after logging in, refreshing a protected route redirects me to the root path ('/'), but when I refresh on a non-protected route like /myaccount, it does not redirect.

Problem Description After logging in, if I navigate to a protected route like /dashboard and refresh the page, I am redirected to '/'. However, when I am on a non-protected route such as /myaccount, refreshing the page does not redirect me.

Desired Behavior I want the application to maintain the current state after a refresh, preventing redirects to the root path ('/') for both protected and non-protected routes.

What I've Tried Checked the token and userType state management in the context. Ensured that the fetchData function correctly retrieves user type after a login.

Code Here’s the relevant code for my application: App.js

import React, { Suspense, lazy } from "react";
import { BrowserRouter, Routes, Route } from "react-router-dom";
import Layout from "./components/layout/Layout";
import { CombinedProvider } from "./contexts/useContext";
import { ToastContainer } from "react-toastify";
import ProtectedRoute from "./utils/ProtectedRoute";

const SelectionPage = lazy(() => import("./pages/SelectionPage"));
const SignupPage = lazy(() => import("./pages/Signup"));
const LoginTanod = lazy(() => import("./pages/LoginTanod"));
const LoginResident = lazy(() => import("./pages/ResidentLogin"));

// Tanod routes
const Dashboard = lazy(() => import("./components/users/tanods/Dashboard"));
const Patrolmap = lazy(() => import("./components/users/tanods/Map"));
const Equipments = lazy(() => import("./components/users/tanods/Equipment"));
const Performance = lazy(() => import("./components/users/tanods/Performance"));
const Schedule = lazy(() => import("./components/users/tanods/Schedule"));
const Incidents = lazy(() => import("./components/users/tanods/Incidents"));
const MyAccount = lazy(() => import("./components/users/tanods/MyAcc"));

// Resident routes
const ResidentRating = lazy(() => import("./components/users/residents/TanodPersonels"));
const ResidentDashboard = lazy(() => import("./components/users/residents/Dashboard"));

function App() {
  return (
    <div className="flex-1 p-6 bg-background text-text">
      <BrowserRouter>
        <CombinedProvider>
          <Suspense fallback={<div>Loading...</div>}>
            <ToastContainer />
            <Routes>
              {/* Public Routes */}
              <Route path="/" element={<SelectionPage />} />
              <Route path="/tanod-login" element={<LoginTanod />} />
              <Route path="/resident-login" element={<LoginResident />} />
              <Route path="/signup" element={<SignupPage />} />
              <Route element={<Layout />}>
              <Route path="/myaccount" element={<MyAccount />}/>
              </Route>
              {/* Protected Routes for Tanod */}
              <Route element={<Layout />}>
                <Route
                  path="/dashboard"
                  element={<ProtectedRoute userTypeAllowed={["tanod"]}><Dashboard /></ProtectedRoute>}
                />
                <Route
                  path="/patrolmap"
                  element={<ProtectedRoute userTypeAllowed={["tanod"]}><Patrolmap /></ProtectedRoute>}
                />
                <Route
                  path="/equipments"
                  element={<ProtectedRoute userTypeAllowed={["tanod"]}><Equipments /></ProtectedRoute>}
                />
                <Route
                  path="/performance"
                  element={<ProtectedRoute userTypeAllowed={["tanod"]}><Performance /></ProtectedRoute>}
                />
                <Route
                  path="/schedule"
                  element={<ProtectedRoute userTypeAllowed={["tanod"]}><Schedule /></ProtectedRoute>}
                />
                <Route
                  path="/incidents"
                  element={<ProtectedRoute userTypeAllowed={["tanod"]}><Incidents /></ProtectedRoute>}
                />
                
              </Route>

              {/* Protected Routes for Residents */}
              <Route element={<Layout />}>
                <Route
                  path="/resident-dashboard"
                  element={<ProtectedRoute userTypeAllowed={["resident"]}><ResidentDashboard /></ProtectedRoute>}
                />
                <Route
                  path="/ratetanod"
                  element={<ProtectedRoute userTypeAllowed={["resident"]}><ResidentRating /></ProtectedRoute>}
                />
              </Route>
            </Routes>
          </Suspense>
        </CombinedProvider>
      </BrowserRouter>
    </div>
  );
}

export default App;


ProtectedRoute.js

import React from "react";
import { Navigate } from "react-router-dom";
import { useCombinedContext } from "../contexts/useContext";

export default function ProtectedRoute({ userTypeAllowed, children }) {
  const { userType, token } = useCombinedContext();

  if (!token || !userTypeAllowed.includes(userType)) {
    return <Navigate to="/" />;
  }
  
  return children;
}

useContext.js

import React, { createContext, useState, useEffect, useContext, useCallback } from 'react';
import { useNavigate } from 'react-router-dom';
import { toast } from 'react-toastify'; 

export const CombinedContext = createContext();

export const CombinedProvider = ({ children }) => {
  const [token, setToken] = useState(localStorage.getItem('token'));
  const [userType, setUserType] = useState(null); 
  const navigate = useNavigate();

  const fetchData = useCallback(async () => {
    // Fetch user data to get userType
  }, []);

  useEffect(() => {
    if (token) {
      fetchData();
    }
  }, [token, fetchData]);

  const logout = () => {
    localStorage.removeItem('token');
    setToken(null);
    setUserType(null);
    navigate('/');
  };

  return (
    <CombinedContext.Provider value={{ token, userType, logout }}>
      {children}
    </CombinedContext.Provider>
  );
};

export const useCombinedContext = () => useContext(CombinedContext);


Solution

  • It sounds like you’re dealing with a state persistence issue in your React application. Here are a few suggestions to help you maintain the current state after a refresh, preventing redirects to the root path (‘/’) for both protected and non-protected routes:

    1. Persisting User State in Local Storage Ensure that your user state (token and userType) is correctly persisted in local storage and rehydrated on app load.

    In your useContext.js, you are already storing the token in local storage. Make sure you also store the userType.

    2. Check User State on Route Change Ensure that your ProtectedRoute component correctly checks the user state before rendering the protected component.

    1. Rehydrate State on App Load Make sure your app rehydrates the state from local storage when it loads. You can do this in your CombinedProvider.

    import React, { createContext, useState, useEffect, useContext, useCallback } from 'react';
    import { useNavigate } from 'react-router-dom';
    import { toast } from 'react-toastify'; 
    
    export const CombinedContext = createContext();
    
    export const CombinedProvider = ({ children }) => {
      const [token, setToken] = useState(localStorage.getItem('token'));
      const [userType, setUserType] = useState(localStorage.getItem('userType')); 
      const navigate = useNavigate();
    
      const fetchData = useCallback(async () => {
        // Fetch user data to get userType
      }, []);
    
      useEffect(() => {
        if (token) {
          fetchData();
          localStorage.setItem('userType', userType);
        }
      }, [token, userType, fetchData]);
    
      useEffect(() => {
        const storedToken = localStorage.getItem('token');
        const storedUserType = localStorage.getItem('userType');
        if (storedToken) {
          setToken(storedToken);
        }
        if (storedUserType) {
          setUserType(storedUserType);
        }
      }, []);
    
      const logout = () => {
        localStorage.removeItem('token');
        localStorage.removeItem('userType');
        setToken(null);
        setUserType(null);
        navigate('/');
      };
    
      return (
        <CombinedContext.Provider value={{ token, userType, logout }}>
          {children}
        </CombinedContext.Provider>
      );
    };
    
    export const useCombinedContext = () => useContext(CombinedContext);