skip to Main Content

I’m trying to route to a component based on a condition and it does it, but only if I refresh the page. I’ve had to add forceUpdate() to get it to work, but that is killing performance. Maybe this is something that would stand out easily for you guys, but for me it is escaping me.

The following route is the one in question:

<Route element={<RequireAuth />}>
  <Route
    path="/"
    element={userrole === "Browser" ? <ViewOnly /> : <InvoiceList2 />}
  />
</Route>

Full code:

import React, { useEffect, useState, useReducer } from "react";
import { BrowserRouter, Routes, Route, Navigate } from "react-router-dom";
import {
  useFetchVendorsQuery,
  useFetchInvoicesQuery,
  useFetchTrackersQuery,
} from "../src/features/Invoices/InvoiceSlice";

const ROLES = {
  User: "user",
  Admin: "Admin",
};

const Wildcard = () => {
  <Navigate to="/login" />;
};

const App = () => {
  // const auth = useAuth();
  // let { user, role, userid } = auth.auth;

  const GetVendors = useFetchVendorsQuery();
  const [vendors, setVendors] = useState([]);

  const GetTrackers = useFetchTrackersQuery();
  const [trackers, setTrackers] = useState([]);

  const GetInvoices = useFetchInvoicesQuery();
  const [invoices, setInvoices] = useState([]);

  const [userrole, setUserRole] = useState("");

  const User = JSON.parse(localStorage.getItem("user"));

  const [ignored, forceUpdate] = useReducer((x) => x + 1, 0);

  const handleOnload = () => {
    forceUpdate();
  };

  useEffect(() => {
    // setVendors(GetVendors);
    // setInvoices(GetInvoices);
    // setTrackers(GetTrackers);
    setUserRole(User?.role);
    forceUpdate();
  }, [userrole, ignored]);

  return (
    <div className="content">
      <BrowserRouter>
        {/* <Header /> */}
        <Routes>
          <Route path="/login" element={<Login />} />
          <Route path="/register" element={<Registration />} />
          <Route element={<RequireAuth />}>
            {/* <Route path="/" element={<InvoiceList2 />} /> */}
            <Route
              path="/"
              element={userrole === "Browser"
                ? <ViewOnly />
                : <InvoiceList2 />
              }
            />
          </Route>
          <Route element={<RequireAuth />}>
            <Route path="/admin" element={<Admin />} />
          </Route>
          <Route element={<RequireAuth />}>
            <Route path="/manual" element={<ManualEntry />} />
          </Route>
          <Route path="/*" element={Wildcard} />
        </Routes>
        <Footer1 />
      </BrowserRouter>
    </div>
  );
};

export default App;

2

Answers


  1. The issue you’re experiencing is likely due to the asynchronous nature of updating the userrole state. The route rendering logic doesn’t pick up the updated userrole because React doesn’t re-render routes unless there is a state change triggering the re-render.

    Make sure that the userrole state is set correctly before rendering the Routes. Use conditional rendering to wait until the userrole is ready

    import React, { useEffect, useState } from "react";
    import { BrowserRouter, Routes, Route, Navigate } from "react-router-dom";
    
    import {
      useFetchVendorsQuery,
      useFetchInvoicesQuery,
      useFetchTrackersQuery,
    } from "../src/features/Invoices/InvoiceSlice";
    
    const ROLES = {
      User: "user",
      Admin: "Admin",
    };
    
    const Wildcard = () => <Navigate to="/login" />;
    
    const App = () => {
      const GetVendors = useFetchVendorsQuery();
      const [vendors, setVendors] = useState([]);
    
      const GetTrackers = useFetchTrackersQuery();
      const [trackers, setTrackers] = useState([]);
    
      const GetInvoices = useFetchInvoicesQuery();
      const [invoices, setInvoices] = useState([]);
      const [userrole, setUserRole] = useState(null); // Initially null to indicate "loading"
    
      // Fetch the user's role from localStorage
      useEffect(() => {
        const User = JSON.parse(localStorage.getItem("user"));
        setUserRole(User?.role || null); // Fallback to null if no role is found
      }, []);
    
      // Wait until userrole is determined before rendering Routes
      if (userrole === null) {
        return <div>Loading...</div>; // Or a spinner
      }
    
      return (
        <div className="content">
          <BrowserRouter>
            <Routes>
              <Route path="/login" element={<Login />} />
              <Route path="/register" element={<Registration />} />
              <Route element={<RequireAuth />}>
                <Route
                  path="/"
                  element={userrole === "Browser" ? <ViewOnly /> : <InvoiceList2 />}
                />
              </Route>
              <Route element={<RequireAuth />}>
                <Route path="/admin" element={<Admin />} />
              </Route>
              <Route element={<RequireAuth />}>
                <Route path="/manual" element={<ManualEntry />} />
              </Route>
              <Route path="/*" element={<Wildcard />} />
            </Routes>
            <Footer1 />
          </BrowserRouter>
        </div>
      );
    };
    
    export default App;
    
    Login or Signup to reply.
  2. Issue

    I suspect the issue is that users are authenticating and this only updates the "user" value in localStorage and nothing else. LocalStorage is non-reactive.

    The issues is basically these lines of code:

    const [userrole, setUserRole] = useState("");
    
    const User = JSON.parse(localStorage.getItem("user"));
    
    useEffect(() => {
      // setVendors(GetVendors);
      // setInvoices(GetInvoices);
      // setTrackers(GetTrackers);
      setUserRole(User?.role);
      forceUpdate();
    }, [userrole, ignored]);
    

    The App component mounts and:

    1. Initializes userrole to ""
    2. Reads "user" value from localStorage and declares a User variable
    3. The useEffect hook runs and updates the userRole state to the current User?.role value, likely null since no user is authenticated

    I suspect the sequence of events goes something along the lines of:

    1. App mounts and the 3 steps above occur
    2. User authenticates and a "user" value is set in localStorage
    3. …. Nothing triggers App to rerender and "see" the updated "user" value in localStorage
    4. You manually reload the page or manually trigger a component rerender to pick up the localStorage value stored into User and the effects runs to update the userrole state

    Solution Suggestion

    Pass the user state updater function down as props to your Login component so that when a user authenticates you update the React state instead of localStorage. If you need to persist that state, then do that in App via a side-effect.

    Example:

    const App = () => {
      ...
    
      // Lazy initialize user state from localStorage
      const [user, setUser] = useState(() => {
        return localStorage.getItem("user")
          ? JSON.parse(localStorage.getItem("user"))
          : null;
      });
    
      // Persist user state changes to localStorage
      useEffect(() => {
        localStorage.setItem("user", JSON.stringify(user));
      }, [user]);
    
      return (
        <div className="content">
          <BrowserRouter>
            ...
            <Routes>
              <Route
                path="/login"
                element={<Login setUser={setUser} />}
              />
              <Route
                path="/register"
                element={<Registration setUser={setUser} />}
              />
              <Route element={<RequireAuth />}>
                <Route
                  path="/"
                  element={user.role === "Browser"
                    ? <ViewOnly />
                    : <InvoiceList2 />
                  }
                />
                <Route path="/admin" element={<Admin />} />
                <Route path="/manual" element={<ManualEntry />} />
              </Route>
              <Route path="*" element={<Navigate to="/login" />} />
            </Routes>
            ...
          </BrowserRouter>
        </div>
      );
    };
    

    The Login and Registration receive setUser as a prop so that when users log in or register you can update the user state in the parent component and trigger a component rerender.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search