Search code examples
javascriptreactjsreact-router-dom

Cannot navigate in react


This is my code to happen when submit button is clicked

const location = useLocation();
const from = location.state?.from?.pathname || "/";

// The above lines are perfectly fine in the parent function

const handleSubmit = async (e) => {
  e.preventDefault();

  loginDetails.userName = user;
  loginDetails.password = pwd;

  try {
    const response = await axios.post(
      LOGIN_URL,
      JSON.stringify({ user, pwd }),
      {
        headers: { 'Content-Type': 'application/json' },
        withCredentials: true,
      }
    );

    const accessToken = response?.data?.accessToken;
    const roles = response?.data?.roles;
    setAuth({ user, pwd, roles, accessToken });
    setUser('');
    setPwd('');

    navigate(from, {replace:true});
  } catch (err) {
    if (!err?.response) {
      console.log(err);
      setErrMsg('No Server Response');
    } else if (err.response?.status === 400) {
      setErrMsg('Missing Username or Password');
    } else if (err.response?.status === 401) {
      setErrMsg('Unauthorized');
    } else {
      setErrMsg('Login Failed');
    }
    errRef.current.focus();
  }
};

This is my Router.js responsible for routing the pages

const Router = () => {
  return (
    <BrowserRouter>
      <Routes>
        <Route path='/' element={<Login />}>
          {/* Public route */}
          <Route path='login' element={<Login />} />
          {/* Private routes */}
          {/* The below is for the user */}
          <Route path='grievance/*' element={<RequireAuth allowedRoles={[ROLES.User]} />}>
            <Route element={<Grievance />} />
          </Route>
          {/* The below is for the admin */}
          <Route path='admin/*' element={<RequireAuth allowedRoles={[ROLES.Admin]} />}>
            <Route element={<Admin />} />
          </Route>
          {/* catch everything else */}
          <Route path='*' element={<Missing />} />
        </Route>
      </Routes>
    </BrowserRouter>
  );
};

This is my RequireAuth.js responsible for checking authorization of the person

const RequireAuth = ({ allowedRoles }) => {
  const { auth } = useAuth();
  const navigate = useNavigate();

  console.log("Here in require auth");

  if (auth?.roles?.find(role => allowedRoles?.includes(role))) {
    console.log("for outlet");
    return <Outlet />;
  } else if (auth?.user) {
    console.log("for unauthorized");
    navigate("/unauthorized", { replace: true });
    return null;
  } else {
    console.log("Authlogin");
    navigate("/login", { replace: true });
    return null;     
  }
};

And the console has the log of only the "navigating to grievance page" and nothing else from RequireAuth.js, and in backend of my code everything is running fine and in the frontend I am responded with all the data correctly. But I am unable to navigate from one page to the other Please help me in this

Grievance is not rendering though it is navigating to "/grievance". I just checked that.

If you are asking for my useAuth(). the code is below

import { createContext, useState } from "react";

const AuthContext = createContext({});

export const AuthProvider = ({ children }) => {
  const [auth, setAuth] = useState({});
  // console.log("auth=",auth);
  return (
    <AuthContext.Provider value={{ auth, setAuth }}>
      {children}
    </AuthContext.Provider>
  )
}

export default AuthContext;

Even after going through the post suggested by the comment, I still could not figure out what went wrong


Solution

  • I suspect the issue here is that you are rendering Login as a layout route component on the root "/" parent route and that Login doesn't render an Outlet component for the nested routes to render out their element content. This appears to agree with the behavior you describe where the URL path changes to "/grievance" but the Login component is still rendered and the nested route isn't reached and so you get none of the logged output from your RequireAuth component.

    Remove element={<Login />} from the root route so an Outlet component is rendered by default. If you don't have any specific "home page" content to display on path "/" then you can remove this route entirely and let the path="*" handle it.

    Example:

    const Router = () => {
      return (
        <BrowserRouter>
          <Routes>
            {/* Public route */}
            <Route path='login' element={<Login />} />
    
            {/* Private routes */}
            {/* The below is for the user */}
            <Route element={<RequireAuth allowedRoles={[ROLES.User]} />}>
              <Route path='grievance' element={<Grievance />} />
            </Route>
    
            {/* The below is for the admin */}
            <Route element={<RequireAuth allowedRoles={[ROLES.Admin]} />}>
              <Route path='admin' element={<Admin />} />
            </Route>
    
            {/* catch everything else */}
            <Route path='*' element={<Missing />} />
          </Routes>
        </BrowserRouter>
      );
    };
    

    The RequireAuth component should render the Navigate component instead of using the navigate function in the render return directly. It should also forward the current location so the Login component has a from value to use from route state.

    const RequireAuth = ({ allowedRoles = [] }) => {
      const location = useLocation();
      const { auth } = useAuth();
    
      if (auth?.roles?.some(role => allowedRoles.includes(role))) {
        return <Outlet />;
      } else if (auth?.user) {
        return <Navigate to="/unauthorized" replace />;
      } else {
        return <Navigate to="/login" replace state={{ from: location }} />;     
      }
    };