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>
);
}