skip to Main Content

I have implemented authorization in React. I want the user to not be able to go to other pages of the application without authorization. I created the following component.

import { ReactNode } from "react";
import { Navigate } from "react-router-dom";
import { useAuth } from "../hooks/use-auth";

interface ProtectedRouteProps {
  children: ReactNode,
}

export const ProtectedRoute: React.FC<ProtectedRouteProps> = ({ children }) => {
  const { isAuth } = useAuth();
  
  return !isAuth ? <Navigate to="/login" replace /> : <>{children}</>;
};

However, when going to authorization page in a reverse redirect, the child component is rendered for a few milliseconds. How to fix it?

<Route 
  path="/kanban" 
  element={
    <ProtectedRoute>
      <KanbanPage />
    </ProtectedRoute>
  }
/>
import { RootState, useAppSelector } from "../store";
        
export function useAuth() {
  const { email, token, id } = useAppSelector((state: RootState) => state.user);
        
  return {
    isAuth: !!email,
    email,
    token,
    id,
  }
}

Auth logic

const onSubmit = (e: React.FormEvent) => {
  e.preventDefault();
  const auth = getAuth();
  signInWithEmailAndPassword(auth, email, password)
    .then(({ user }) => {
      dispatch(setUser({
        email: user.email,
        id: user.uid,
        token: user.refreshToken,
      }));
      setOpenSnackbar(true);
      setTimeout(() => navigate('/kanban'), 2000)
    })
    .catch((error) => {
      console.log(error);
    });
}

redux:

import { createSlice } from "@reduxjs/toolkit";
    
const initialState = {
  email: null,
  token: null,
  id: null
};

const userSlice = createSlice({
  name: 'user',
  initialState,
  reducers: {
    setUser(state, action) {
      state.email = action.payload.email;
      state.token = action.payload.token;
      state.id = action.payload.id;
    },
    removeUser(state) {
      state.email = null;
      state.token = null;
      state.id = null;
    }
  }
});

export const { setUser, removeUser } = userSlice.actions;

export default userSlice.reducer;

2

Answers


  1. you have to enter the isAuth to useMemo and only when the isAuth will changed the component rerendered.

    Login or Signup to reply.
  2. The isAuth is incorrectly returning true or false when the auth status isn’t known yet.

    Update the setUser reducer to set an isAuth state value instead of coercing the email value to a boolean.

    const userSlice = createSlice({
      name: "user",
      initialState: {
        isAuth: undefined, // <-- initially undefined
        email: "",
        token: "",
        id: "",
      },
      reducer: {
        setUser: (state, action) => {
          const { email, token, id } = action.payload;
          state.isAuth = !!email; // <-- set computed isAuth here
          state.email = email;
          state.token = token;
          state.id = id;
        },
        removeUser: (state) => {
          state.isAuth = false;
          state.email = null;
          state.token = null;
          state.id = null;
        }
      },
    });
    

    Update ProtectedRoute to conditionally render null or a loading indicator when the isAuth value isn’t set/determined yet.

    export function useAuth() {
      const { isAuth, email, token, id } = useAppSelector(
        (state: RootState) => state.user
      );
        
      return { isAuth, email, token, id };
    }
    
    export const ProtectedRoute = () => {
      const { isAuth } = useAuth();
    
      if (isAuth === undefined) {
        return null; // or loading indicator/spinner/etc
      
      return isAuth
        ? <Outlet />
        : <Navigate to="/login" replace />;
    };
    
    <Route element={<ProtectedRoute />}>
      <Route path="/kanban" element={<KanbanPage />} />
      ... other protected routes ...
    </Route>
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search