The Protected Routes in React Router Dom persistently redirecting

I am currently implementing protected routes in my Redux and Pure React app using React Router Dom. The issue I am facing is that when I navigate to /profile/edit and then refresh the page, it automatically redirects me to /login and then to /profile. I have set up a protected route but I want to ensure that when I am on an isAuthenticated page, I stay on that page without being redirected to /profile. Initially, I placed my protectedRoute component inside the element prop which caused some problems. I then tried placing it within a loader component, but now the protected route does not seem to work properly. Can anyone provide some guidance on how to achieve this or offer any suggestions? Thank you.

// App.js

<Routes>
  <Route
    element={
      <ProtectedRoute
        isAuthenticatedRoute={false}
        sendTo={"/profile"}
      />
    }
  >
    <Route path="/login" element={<Login />} />
    <Route path="/register" element={<Register />} />
  </Route>

  {/* Profile Pages */}
  <Route
    element={
      <ProtectedRoute
        isAuthenticatedRoute={true}
        sendTo={"/login"}
      />
    }
  >
    <Route path="/profile" element={<Profile />} />
    <Route path="/profile/category" element={<EditCategory />} />
    <Route path="/profile/edit" element={<EditProfile />} />
  </Route>

  {/* Page Not Found */}
  <Route path="/*" element={<PageNotFound />} />
</Routes>

// App.js with loader

<Routes>
  <Route
    loader={
      <ProtectedRoute
        isAuthenticatedRoute={false}
        sendTo={"/profile"}
      />
    }
  >
    <Route path="/login" element={<Login />} />
    <Route path="/register" element={<Register />} />
  </Route>
</Routes>

// ProtectedRoutes.js

import React, { useEffect } from "react";
import { useSelector } from "react-redux";
import { Navigate, Outlet, useNavigate } from "react-router-dom";
import AppLoader from "../AllLoader/AppLoader/AppLoader";
import { useLoadUserQuery } from "../../redux/feature/apiSlice/apiSlice";

const ProtectedRoute = ({
  isAuthenticatedRoute,
  children,
  adminRoute,
  isAdmin,
  sendTo,
}) => {
  const navigate = useNavigate();
  const { isLoading } = useLoadUserQuery(undefined, undefined);

  const { user } = useSelector((state) => state.auth);

  if (isLoading) {
    return <AppLoader />;
  }

  const isAuthenticated = user;

  if (isAuthenticatedRoute && !isAuthenticated) {
    return <Navigate to="/login" replace={true} />;
  }
  if (!isAuthenticatedRoute && isAuthenticated) {
    return <Navigate to="/profile" replace={true} />;
  }

  return children ? children : <Outlet />;
};

export default ProtectedRoute;

The purpose of this implementation is to ensure that when accessing any authenticated page, the user remains on that page rather than being redirected initially from /login to /profile.

Answer №1

I have a suspicion that the query useLoadUserQuery doesn't set the status of isLoading to true until after the initial render cycle is completed. This could mean that during the initial render cycle, isLoading is false and the ProtectedRoute component uses the initial state value of null for the selected state.auth.user, resulting in the redirection of the user through one of the Navigate components.

My suggestion would be to initialize state.auth.user with an indeterminate value like undefined and also explicitly check for this value during loading.

For example:

const userSlice = createSlice({
  name: "user",
  initialState: {
    authLoading: false,
    getUserProfileLoading: false,
    refreshTokenLoading: false,
    user: undefined, // <-- initially undefined
    userActivationToken: "",
    authError: null,
    authMessage: null,
    getUserProfileMessage: null,
    getUserProfileError: null,
  },
  reducers: {
    ...
  },
  extraReducers: (builder) => {
    ...
  },
});
const ProtectedRoute = ({
  isAuthenticatedRoute,
  children,
  adminRoute,
  isAdmin,
  sendTo,
}) => {
  const navigate = useNavigate();
  const { isLoading } = useLoadUserQuery(undefined, undefined);

  const user = useSelector((state) => state.auth.user);

  if (isLoading || user === undefined) { // <-- loading, or undefined user
    return <AppLoader />;
  }

  const isAuthenticated = !!user;

  if (isAuthenticatedRoute && !isAuthenticated) {
    return <Navigate to="/login" replace />;
  }
  if (!isAuthenticatedRoute && isAuthenticated) {
    return <Navigate to="/profile" replace />;
  }

  return children ?? <Outlet />;
};

A more traditional approach to route protection implementation would separate the logic for protecting routes from unauthenticated users and authenticated users. Modify ProtectedRoute to redirect only unauthenticated users, and create another component specifically for routes related to login.

For example:

const ProtectedRoute = ({ children }) => {
  const navigate = useNavigate();
  const { isLoading } = useLoadUserQuery(undefined, undefined);

  const user = useSelector((state) => state.auth.user);

  if (isLoading || user === undefined) {
    return <AppLoader />;
  }

  const isAuthenticated = !!user;
  
  if (!isAuthenticated) {
    return <Navigate to="/login" replace />;
  }

  return children ?? <Outlet />;
};
const AnonymousRoute = ({ children }) => {
  const navigate = useNavigate();
  const { isLoading } = useLoadUserQuery(undefined, undefined);

  const user = useSelector((state) => state.auth.user);

  if (isLoading || user === undefined) {
    return <AppLoader />;
  }

  const isAuthenticated = !!user;

  if (isAuthenticated) {
    return <Navigate to="/profile" replace />;
  }

  return children ?? <Outlet />;
};
<Routes>
  <Route element={<AnonymousRoute />}>
    <Route path="/login" element={<Login />} />
    <Route path="/register" element={<Register />} />
  </Route>

  <Route element={<ProtectedRoute/>}>
    <Route path="/profile" element={<Profile />} />
    <Route path="/profile/category" element={<EditCategory />} />
    <Route path="/profile/edit" element={<EditProfile />} />
  </Route>

  <Route path="/*" element={<PageNotFound />} />
</Routes>

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Using JavaScript to transform JSON information into Excel format

I have tried various solutions to my problem, but none seem to fit my specific requirement. Let me walk you through what I have attempted. function JSONToCSVConvertor(JSONData, ReportTitle, ShowLabel) { //If JSONData is not an object then JSON.parse will ...

Can you please explain the distinction between the statements var a = b = 2 and var a = 2; var b = 2;

Whenever I try to declare a variable within a function, I encounter an issue. var b = 44; function test(){ var a = b = 2; } However, the following code works without any problems: var b = 44; function test(){ var a; var b = 2; } The global ...

Manually assigning a value to a model in Angular for data-binding

Currently utilizing angular.js 1.4 and I have a data-binding input setup as follows: <input ng-model="name"> Is there a way to manually change the value without physically entering text into the input field? Perhaps by accessing the angular object, ...

What is the best way to eliminate duplicate values from an Array in ReactJS?

Hi there, I'm new to JavaScript and React. I need some help with a project I found on the React blog. I want to try solving it in my own way. This is the content of todoList.js: const todoList = [ {category: 'Sporting Goods', price: &a ...

The onClick event in monaco-editor/react is not triggering as expected

I've successfully integrated monaco-editor/react into my Next.js application, and all is working smoothly. Now, I am facing an issue with the onClick event of the editor not firing. Here's the code snippet: import Editor from "@monaco-editor ...

What is the proper way to utilize setTimeout in TypeScript?

Let's take a look at an example of how to use setTimeout in Angular and TypeScript: let timer: number = setTimeout(() => { }, 2000); However, upon compilation, you may encounter the following error message: Error TS2322: Type 'Timeout' ...

Dynamic updating of scores using Ajax from user input

My goal is to design a form that includes three "Likert Scale" input fields. Each of these three inputs will have a total of 10 points that can be distributed among them. The submit button should become enabled when the score reaches 0, allowing users to s ...

JavaScript is proving to be uncooperative in allowing me to modify the

Despite searching through previously asked questions, I have been unable to find a solution to my issue. I am struggling with changing an image source upon clicking the image itself. The following is a snippet of my HTML code: <img id="picture1" oncli ...

Issues with obtaining the SSO authentication token from Microsoft Teams

Seeking assistance with obtaining the access token from Microsoft Teams. It works flawlessly in Postman with the same data, but when attempting to make a POST request in a React app, it fails. Below is the code snippet: useEffect(() => { debugger ...

React Checkbox malfunctioning: Troubleshooting and solutions

I have thoroughly researched for a solution to my issue before resorting to posting this question. Unfortunately, none of the answers I found seemed to resolve my problem. Despite trying various methods such as changing, clicking, and checking, my checkbo ...

What could be causing these transformed canvases to not display fully in Chrome at specific resolutions?

fiddle: https://jsfiddle.net/f8hscrd0/66/ html: <body> <div id="canvas_div"> </div> </body> js: let colors = [ ['#000','#00F','#0F0'], ['#0FF','#F00','#F0F&a ...

Customizing font size in React with Material UI: A comprehensive guide on adjusting the font size of the Select component

I'm currently working on a web application that utilizes React along with Material UI. My goal is to adjust the font size of the Select component. I've attempted to achieve this by utilizing the MenuProps property, as shown in the following code ...

Transforming color images into black and white using JavaScript

     I have implemented this code to convert colored images to grayscale. function applyGrayscaleEffect() {         var imageData = contextSrc.getImageData(0, 0, width, height);         var data = imageData.data;         var p1 = 0.99;   ...

A guide on accessing Textfield input values in Redux-form while utilizing MaterialUI

The objective is to retrieve input values from a material UI component and transmit them to an action creator within a handleSubmit function. <Field name='email' component={email => <TextField fullWidth autoComplete='off' clas ...

Error: The module 'https://www.gstatic.com/firebasejs/9.6.0/firebase-app.js' is missing the required export 'default'

I'm currently in the process of setting up and testing Google authentication for a web application that I'm developing. Unfortunately, I've encountered several issues with getting the JavaScript functions to work properly, and I am uncertain ...

Utilize getElementsByClassName to dynamically alter the appearance of specific elements upon triggering an event

I'm attempting to utilize onmouseover="document.getElementsByClassName().style.background='color'" in order to switch the color of all divs with a specified classname to a different color when hovering over another element on the page. ...

"Exciting features of NextJS 13 include the utilization of client component callback functions

Let's say I need to develop a custom Table React component with server-side pagination and sorting features, which can be reused in different parts of the application to display various types of data. In my create-react-app project, I would create a ...

Issue with retrieving the ID of a dynamically created element with jQuery

Whenever I try to execute my function to display the id for a click event of a tag that has items appended dynamically, the alert does not show the id. Instead, it displays 'undefined'. Can anyone help me figure out where I am going wrong? Here ...

Tips for adding styles to MUI MenuList using styled components

I have a requirement to change the background-color of the MenuItem component when its selected prop is set to true. In addition, I want to add a style to the MenuItem component when it is hovered over. How can I achieve this? import * as React from " ...

Creating a universal header for all webpages in Angular JS by dynamically adding elements using JavaScript DOM manipulation techniques

I have successfully created a json File containing an array of "learnobjects", each including an id, name, and link. Check out my plnkr example var myMessage = { "learnobjects": [{ "id": "1", "name": "Animation-Basics", "link": "animation_bas ...