How to use route objects (react-router v6) for authentication to prevent hooks errors

Viewed 46

I am trying to make authentication for a React JS project with react router v6, but I am getting the following error: When I sign in:

Warning: React has detected a change in the order of Hooks called by App. This will lead to bugs and errors if not fixed.

Rendered more hooks than during the previous render.

When I sign out:

Warning: React has detected a change in the order of Hooks called by App. This will lead to bugs and errors if not fixed.

Rendered fewer hooks than expected. This may be caused by an accidental early return statement.

Here's my code: Routes.jsx

import AddNewProductPage from "pages/AddNewProductPage"
import React from "react"
import { Navigate, Route, useLocation } from "react-router-dom"

import {
  Homepage,
  SignInPage,
  ProductListManagement,
  ProductDetailPage,
  SignUpPage,
  ProductListPage,
  ForgetPasswordPage,
  ShoppingCartPage,
} from "pages"
import AdminPage from "pages/AdminPage"
import LoadingSpinner from "components/LoadingSpinner/LoadingSpinner"
import { useSelector } from "react-redux"

const DefaultComponent = () => {
  return <Navigate to={Routes.home.path} />
}

export const Routes = {
  signIn: {
    path: "/sign-in",
    element: SignInPage,
  },
  signUp: {
    path: "/sign-up",
    element: SignUpPage,
  },
  forgetPassword: {
    path: "/forget-password",
    element: ForgetPasswordPage,
  },
  admin: {
    path: "/admin",
    element: AdminPage,
    isAuth: true,
    isAdmin: true,
    index: ProductListManagement,
    routes: {
      productList: {
        path: "products",
        element: ProductListManagement,
        isAuth: true,
      },
      addProduct: {
        path: "add-product",
        element: AddNewProductPage,
        isAuth: true,
      },
      editProduct: {
        path: "edit-product/:id",
        element: AddNewProductPage,
        isAuth: true,
      },
      default: {
        path: "*",
        element: DefaultComponent,
      },
    },
  },
  shoppingCart: {
    path: "/cart",
    isAuth: true,
    element: ShoppingCartPage,
    index: ShoppingCartPage,
    routes: {
      default: {
        path: "*",
        element: ShoppingCartPage,
      },
    },
  },
  home: {
    path: "/",
    element: Homepage,
    index: ProductListPage,
    routes: {
      products: {
        path: "products",
        element: ProductListPage,
      },
      productDetail: {
        path: "products/:id",
        element: ProductDetailPage,
      },
      default: {
        path: "*",
        element: DefaultComponent,
      },
    },
  },
  default: {
    path: "*",
    element: DefaultComponent,
  },
}

export function AppLoading(props) {
  return <Route path={props.path} element={<LoadingSpinner />} />
}

export function RouteComponentWrapper(route, key) {
  const user = useSelector((state) => state.user)
  let location = useLocation()
  if (!user.isAuth && route.isAuth)
    return (
      <Route
        key={key}
        path={route.path}
        element={
          <Navigate to={Routes.signIn.path} state={{ from: location }} />
        }
      />
    )

  if (!user.isAdmin && route.isAdmin) {
    if (user.isAuth) {
      return (
        <Route
          key={key}
          path={route.path}
          element={
            <Navigate to={Routes.home.path} state={{ from: location }} />
          }
        />
      )
    } else {
      return (
        <Route
          key={key}
          path={route.path}
          element={
            <Navigate to={Routes.signIn.path} state={{ from: location }} />
          }
        />
      )
    }
  }

  return (
    <Route path={route.path} element={<route.element />} key={key}>
      {route.index ? <Route index element={<route.index />} /> : undefined}
      {route.routes ? RenderRoutes(route.routes) : undefined}
    </Route>
  )
}

export function RenderRoutes(routes) {
  return Object.values(routes).map((route, index) => {
    console.log(index, route.path)
    return RouteComponentWrapper(route, index)
  })
}

userSlice.js

import { createSlice } from "@reduxjs/toolkit"

const initialState = {
  userInfo: {
    username: "",
    email: "",
  },
  token: "",
  isAuth: false,
  isAdmin: false,
}

export const userSlice = createSlice({
  name: "user",
  initialState,
  reducers: {
    signIn: (state, action) => {
      state.userInfo.email = action.payload.userInfo.email
      state.token = action.payload.token
      state.isAuth = true
      state.isAdmin = true
    },
    signInAsAdmin: (state, action) => {
      state.userInfo = action.payload.userInfo
      state.token = action.payload.token
      state.isAuth = true
      state.isAdmin = true
    },
    signOut: () => {
      //state.isAuth = false
      return initialState
    },
    forgetPassword: () => {},
  },
})

export const { signIn, signInAsAdmin, signOut, forgetPassword } =
  userSlice.actions

export default userSlice.reducer

App.jsx

import React from "react"
import { Routes as RoutesDom } from "react-router-dom"
import { QueryClient, QueryClientProvider } from "react-query"
import { RenderRoutes } from "routes/Routes"
import { Routes } from "routes/Routes"

const queryClient = new QueryClient()

function App() {
  return (
    <QueryClientProvider client={queryClient}>
      <RoutesDom>{RenderRoutes(Routes)}</RoutesDom>
    </QueryClientProvider>
  )
}

export default App

Some console.log if necessary

Sign in

//First render
Routes.jsx:170 0 '/sign-in'
Routes.jsx:170 1 '/sign-up'
Routes.jsx:170 2 '/forget-password'
Routes.jsx:170 3 '/admin'
Routes.jsx:170 4 '/cart'
Routes.jsx:170 5 '/'
Routes.jsx:170 0 'products'
Routes.jsx:170 1 'products/:id'
Routes.jsx:170 2 '*'
Routes.jsx:170 6 '*'

//Second render
Routes.jsx:170 0 '/sign-in'
Routes.jsx:170 1 '/sign-up'
Routes.jsx:170 2 '/forget-password'
Routes.jsx:170 3 '/admin'
Routes.jsx:170 0 'products'
Routes.jsx:170 1 'add-product'
Routes.jsx:170 2 'edit-product/:id'
Routes.jsx:170 3 '*'
Routes.jsx:170 4 '/cart'
Routes.jsx:170 0 '*'
Routes.jsx:170 5 '/'
Error

Sign out

//First render
Routes.jsx:170 0 '/sign-in'
Routes.jsx:170 1 '/sign-up'
Routes.jsx:170 2 '/forget-password'
Routes.jsx:170 3 '/admin'
Routes.jsx:170 0 'products'
Routes.jsx:170 1 'add-product'
Routes.jsx:170 2 'edit-product/:id'
Routes.jsx:170 3 '*'
Routes.jsx:170 4 '/cart'
Routes.jsx:170 0 '*'
Routes.jsx:170 5 '/'
Routes.jsx:170 0 'products'
Routes.jsx:170 1 'products/:id'
Routes.jsx:170 2 '*'
Routes.jsx:170 6 '*'

//Second render
Routes.jsx:170 0 '/sign-in'
Routes.jsx:170 1 '/sign-up'
Routes.jsx:170 2 '/forget-password'
Routes.jsx:170 3 '/admin'
Routes.jsx:170 4 '/cart'
Routes.jsx:170 5 '/'
Routes.jsx:170 0 'products'
Routes.jsx:170 1 'products/:id'
Routes.jsx:170 2 '*'
Routes.jsx:170 6 '*'
Error
0 Answers
Related