Home > other >  Building context-nested routes with React Router 6
Building context-nested routes with React Router 6

Time:11-23

Recently I've asked a question related to the same topic, but this time I stumbled in another problem. I did some refactoring in my code as told by Andrew in this post and worked, but I can't seem to reach the ClientRoutes.

The login and authentication are working in both, and I can access the ProRoute's, but when I try to do with a client user I stay in the login page, even after the login and authentication check and retrieving the user data.

So, my questions are: Building two separate '/home' routes would work (like '/home/pro/' and '/home/client')? The same path ('/home') works for two different components (<HomePro /> and <HomeClient />), since there are two contexts (private/protected routes)? Is there something wrong with my nested routing? Thanks in advance!

These are my files. I'm using react-router v6. First App_router (I put the AuthProvider wrapping the App component)

import React from 'react';
import { Route, Routes } from "react-router-dom";

import { ProRoute } from './ProRoute';
import { ClientRoute } from './ClientRoute';

import HomePro from '../Pages/HomePro';
import HomeClient from '../Pages/HomeClient';
import Exercises from '../Pages/Exercises';
import UserForm from '../Pages/Forms/UserForm';
import Login from '../Pages/Login';

const App_Router = () => {
  return (
    <Routes>
      <Route index element={<Login />} />
      <Route path='/login' element={<Login />} />
      <Route path='/register' element={<UserForm />} />
      <Route path='/home'>
        <Route element={<ProRoute />}>
          <Route index element={<HomePro />} />
          <Route path='exercicios' element={<Exercises />} />
        </Route>
        <Route element={<ClientRoute />}>
          <Route index element={<HomeClient />} />
          <Route path='exercicios' element={<Exercises />} />
        </Route>
      </Route>
    </Routes>
  )
}

export default App_Router;

ProRoute

import React from 'react'
import { Navigate, Outlet } from 'react-router-dom';
import { useContext } from 'react';
import { AuthContext } from '../Contexts/AuthContext';

export const ProRoute = ({ children }) => {
  const { isAuthenticated, loggedUser, isLoading } = useContext(AuthContext);
  
  if (isLoading) {
    return <section>Carregando...</section>;
  }
  
  if (isAuthenticated && loggedUser.data.type == "Profissional") {
    return children || <Outlet />;
  } else {
    return <Navigate to='/login' replace />;
  }
}

ClientRoute

import React from 'react'
import { Navigate, Outlet } from 'react-router-dom';
import { useContext } from 'react';
import { AuthContext } from '../Contexts/AuthContext';

export const ClientRoute = ({ children }) => {
  const { isAuthenticated, loggedUser, isLoading } = useContext(AuthContext);

  if (isLoading) {
    console.log("aqui");
    return <aside>Carregando...</aside>;
  }

  if (isAuthenticated && loggedUser.data.type == "Cliente") {
    return children || <Outlet />;
  } else {
    return <Navigate to='/login' replace />;
  }
}

And the handleLogin method on Login page

const handleSubmit = async (e) => {
  e.preventDefault();
  await login(values)
    .then( res => {
      setValues({
        email: '',
        senha: '',
        tipoUsuario: ''
      });
      navigate('/home', {replace: true});
    })
    .catch(error => console.log(error));
}

CodePudding user response:

The issue here is that the code is attempting to render two routes on the same path, but only one route can match per path.

const App_Router = () => {
  return (
    <Routes>
      <Route index element={<Login />} />
      <Route path='/login' element={<Login />} />
      <Route path='/register' element={<UserForm />} />
      <Route path='/home'>
        <Route element={<ProRoute />}>
          <Route index element={<HomePro />} />    // <-- "/home"
          <Route path='exercicios' element={<Exercises />} />
        </Route>
        <Route element={<ClientRoute />}>
          <Route index element={<HomeClient />} /> // <-- "/home"
          <Route path='exercicios' element={<Exercises />} />
        </Route>
      </Route>
    </Routes>
  );
}

react-router-dom@6 uses a Route Ranking System and selects the best match. You will need to differentiate/disambiguate these routes/paths similar to what it seems you allude to. The "/home" route rendering HomeClient is masked by the "/home" route rendering "HomePro" and isn't reachable.

So, my questions are: Building two separate '/home' routes would work (like '/home/pro/' and '/home/client')?

Yes, these two paths would be enough to disambiguate the client and pro routes.

const App_Router = () => {
  return (
    <Routes>
      <Route index element={<Login />} />
      <Route path='/login' element={<Login />} />
      <Route path='/register' element={<UserForm />} />
      <Route path='/home'>
        <Route element={<ProRoute />}>
          <Route path="pro" element={<HomePro />} />       // <-- "/home/pro"
          <Route path='exercicios' element={<Exercises />} />
        </Route>
        <Route element={<ClientRoute />}>
          <Route path="client" element={<HomeClient />} /> // <-- "/home/client"
          <Route path='exercicios' element={<Exercises />} />
        </Route>
      </Route>
    </Routes>
  );
}

If you don't want to inject "../pro/.."|"../client/.." into the URL path then an alternative is to rethink the authentication a bit. Have a single authentication component to protect routes, and a separate pro/client component to render the correct content/layout.

Example:

export const ProtectedRoute = () => {
  const { isAuthenticated, isLoading } = useContext(AuthContext);
    
  if (isLoading) {
    return <section>Carregando...</section>;
  }
    
  if (isAuthenticated) {
    return <Outlet />;
  } else {
    return <Navigate to='/login' replace />;
  }
};
export const ProClientRoute = ({ client, professional }) => {
  const { loggedUser, isLoading } = useContext(AuthContext);

  if (isLoading) {
    return <section>Carregando...</section>;
  }

  switch(loggedUser.data.type) {
    case "Profissional":
      return professional;

    case "Cliente":
      return client;

    default:
      return <Navigate to='/' replace />;
  }
};
const App_Router = () => {
  return (
    <Routes>
      <Route index element={<Login />} />
      <Route path='/login' element={<Login />} />
      <Route path='/register' element={<UserForm />} />
      <Route element={<ProtectedRoute />}>
        <Route
          path="/home"
        >
          <Route
            index
            element={(
              <ProClientRoute
                client={<HomeClient />}
                professional={<HomePro />}
              />
            )}
          />
          <Route path='exercicios' element={<Exercises />} />
        </Route>
      </Route>
    </Routes>
  );
}
  • Related