¿Cuál es la forma correcta de escribir un ProtectedRoute con la nueva versión 6 de react-router? Escribí este, pero no es una ruta.
const PrivateRoute = ({ component: Component, ...props }) => { if (!Component) return null; return props.isAuthenticated ? <Component /> : <Navigate to={props.redirectLink} /> } export default PrivateRoute;Necesitará escribir un contenedor pequeño y usar el componente Navigate para redirigir. También necesitas renderizar una ruta.
const Container = ({Component, redirectLink, isAuthenticated, ...props}) => { if(!isAuthenticated) { return <Navigate to={redirectLink} />; } return <Component {...props} /> } const PrivateRoute = ({ component: Component, redirectLink, isAuthenticated, path, ...props }) => { return ( <Route path={path} element={<Container redirectLink={redirectLink} isAuthenticate={isAuthenticated} Component={Component} />} /> ) export default PrivateRoute; Puede encontrar las pautas de migración aquí en the github docs
Aquí está mi ejemplo de trabajo para implementar rutas privadas usando useRoutes .
Aplicación.js
import routes from './routes'; import { useRoutes } from 'react-router-dom'; function App() { const { isLoggedIn } = useSelector((state) => state.auth); const routing = useRoutes(routes(isLoggedIn)); return ( <> {routing} </> ); }rutas.js
import { Navigate,Outlet } from 'react-router-dom'; const routes = (isLoggedIn) => [ { path: '/app', element: isLoggedIn ? <DashboardLayout /> : <Navigate to="/login" />, children: [ { path: '/dashboard', element: <Dashboard /> }, { path: '/account', element: <Account /> }, { path: '/', element: <Navigate to="/app/dashboard" /> }, { path: 'member', element: <Outlet />, children: [ { path: '/', element: <MemberGrid /> }, { path: '/add', element: <AddMember /> }, ], }, ], }, { path: '/', element: !isLoggedIn ? <MainLayout /> : <Navigate to="/app/dashboard" />, children: [ { path: 'login', element: <Login /> }, { path: '/', element: <Navigate to="/login" /> }, ], }, ]; export default routes;Aquí hay un ejemplo de trabajo.
import React from 'react'; import { Route, Navigate } from 'react-router-dom'; const PrivateRoute = ({ component: Component, redirectTo, isAuth, path, ...props }) => { if(!isAuth) { return <Navigate to={redirectTo} />; } return <Route path={path} element={<Component />} /> }; export default PrivateRoute;Uso:
<Routes> <Route path="app" element={<DashboardLayout />}> <PrivateRoute isAuth={true} path="account" component={AccountView} redirectTo='/login'/> </Route> </Routes>Tomé este ejemplo de react-router-dom : https://github.com/remix-run/react-router/blob/main/examples/auth/README.md
Luego modifique este https://stackblitz.com/edit/github-5kknft?file=src%2FApp.tsx
export default function App() { return ( <AuthProvider> <Routes> <Route element={<Layout />}> <Route path="/" element={<PublicPage />} /> <Route path="/public" element={<PublicPage />} /> <Route path="/login" element={<LoginPage />} /> <Route element={<RequireAuth />}> <Route path="/protected" element={<ProtectedPage />} /> <Route path="/dashboard" element={<Dashboard />} /> </Route> </Route> <Route path="*" element={<NotFound />} /> </Routes> </AuthProvider> ); } function RequireAuth() { let auth = useAuth(); let location = useLocation(); if (!auth.user) { // Redirect them to the /login page, but save the current location they were // trying to go to when they were redirected. This allows us to send them // along to that page after they login, which is a nicer user experience // than dropping them off on the home page. return <Navigate to="/login" state={{ from: location }} />; } return <Outlet />; }Aquí hay una guía oficial de la documentación de React Router .
En lugar de crear contenedores para sus elementos
<Route>para obtener la funcionalidad que necesita, debe hacer toda su propia composición en la propiedad<Route element>.Tomando el ejemplo anterior, si quisiera proteger ciertas rutas de usuarios no autenticados en React Router v6, podría hacer algo como esto:
import { Routes, Route, Navigate } from "react-router-dom"; function App() { return ( <Routes> <Route path="/public" element={<PublicPage />} /> <Route path="/protected" element={ // Good! Do your composition here instead of wrapping <Route>. // This is really just inverting the wrapping, but it's a lot // more clear which components expect which props. <RequireAuth redirectTo="/login"> <ProtectedPage /> </RequireAuth> } /> </Routes> ); } function RequireAuth({ children, redirectTo }) { let isAuthenticated = getAuth(); return isAuthenticated ? children : <Navigate to={redirectTo} />; }Observe cómo, en este ejemplo, el componente
RequireAuthno espera ninguna de las propiedades de<Route>. Esto se debe a que no intenta actuar como<Route>. En cambio, solo se representa dentro de una<Route>.
Aquí hay una implementación un poco más amigable con TypeScript que reutiliza RouteProps de react-router v6:
import React from 'react'; import { RouteProps } from 'react-router'; import { Route, Navigate } from 'react-router-dom'; import { useAuthState } from '../../contexts'; export interface PrivateRouteProps extends RouteProps { redirectPath: string; } export const PrivateRoute = ({ redirectPath, ...props }: PrivateRouteProps) => { const { user } = useAuthState(); if (!user) { return <Navigate to={redirectPath} />; } return <Route {...props} />; }; useAuthState es un enlace que puede recuperar al usuario si uno ha iniciado sesión.
Así es como lo uso:
<Routes> <Route path="/" element={<Home />} /> <PrivateRoute path="/admin" redirectPath="/signin" element={<Admin />} /> <Route path="*" element={<NotFound />} /> </Routes>Todas buenas opciones. También puede simplemente representar el manejo de rutas diferentes según el estado de autenticación (o cualquier otro estado). No tiene que usar el método de objeto Javascript sin formato.
No olvide que puede usar una función interna anónima invocada inmediatamente (() => COMPONENT)() para decidir dinámicamente qué componente maneja una <Route/> particular.
Es posible que los ejemplos aún no estén en la documentación preliminar para v6 porque el manejo de <Route/> privados es en realidad sorprendentemente simple.
P.ej
<Routes> {state.authed ? // Wait until we have the current user... currentUser ? <Route path='/' element={(() => { // Show a "no access" message if the user is NOT an App Admin doesn't have access to any schools at all (which includes not having access to anything INSIDE any school either) if (!currentUser.appAdministrator && currentUser.schoolIds?.length === 0) return <AdminNoAccess /> return <Outlet /> })()} > <Route path='/' element={(() => { // If the user is a super user, we return the <SuperAdmin /> component, which renders some of its own routes/nav. if (currentUser.appAdministrator) return <SuperAdmin /> return <Outlet /> })()} > <Route path='schools' element={(() => { if (currentUser.schoolIds?.length === 1) { return <Navigate to={`schools/schoolId`} /> } else { return <AdminSchools /> } })()} /> <Route path='users' children={<Users />} /> </Route> <Route path={`schools/:schoolId`} element={<AdminSchool />} /> <Route path='*' element={<Navigate to='schools' />} /> </Route> : null : <> <Route path='login' element={<Login />} /> <Route path='signup' element={<Signup />} /> <Route path='forgot-password' element={<ForgotPassword />} /> <Route path='reset-password' element={<ResetPassword />} /> <Route path='*' element={<Navigate to='login' />} /> </> } </Routes>Traté de usar todas las formas mencionadas anteriormente, pero no sé por qué nada funcionó para mí. Finalmente lo resolví y aquí está mi solución a la misma:
Primero cree un archivo con el nombre AdminRoute.js en la carpeta "rutas" en algún lugar de src.
import { Typography } from "@mui/material"; import React, { useEffect, useState } from "react"; import { useSelector } from "react-redux"; import { Navigate } from "react-router-dom"; import { currentAdmin } from "../../functions/auth"; import LoadingToRedirect from "./LoadingToRedirect"; const AdminRoute = ({ children }) => { const { user } = useSelector((state) => ({ ...state, })); const [ok, setOk] = useState(false); useEffect(() => { if (user && user.token) { currentAdmin(user.token) .then((res) => { console.log("CURRENT ADMIN RES", res); setOk(true); }) .catch((err) => { console.log("ADMIN ROUTE ERR", err); setOk(false); }); } }, [user]); return ok ? children : <LoadingToRedirect />; }; export default AdminRoute;Aquí puede tener su propia lógica para decidir cuándo se redirigirá al usuario y cuándo no. Como en mi caso, estoy comprobando si el rol del usuario es administrador o no haciendo una llamada API.
Luego cree un archivo LoadingToRedirect.js en la misma carpeta de "rutas".
import React, { useState, useEffect } from "react"; import { useNavigate } from "react-router-dom"; const LoadingToRedirect = () => { const [count, setCount] = useState(5); let navigate = useNavigate(); useEffect(() => { const interval = setInterval(() => { setCount((currentCount) => --currentCount); }, 1000); // redirect once count is equal to 0 count === 0 && navigate("/"); // cleanup return () => clearInterval(interval); }, [count, navigate]); return ( <div className="container p-5 text-center"> <p>Redirecting you in {count} seconds</p> </div> ); }; export default LoadingToRedirect;Ahora configure su App.js en su app.js:
Aquí, cuando vaya a la URL '/verificar', las funcionalidades de la ruta privada entrarán en acción y verificará si el usuario es 'administrador' o no. Aquí está la página que debe protegerse y actúa como 'hijos' para
<Routes> <Route path="/check" element={ <AdminRoute> <Check /> </AdminRoute> } /> <Route path="*" element={<NotFound />} /> </Routes>Eso es todo Estás listo para ir. ¡¡Salud!!
Esta es la estructura del BrowserRouter como enrutador:
const AppRouter = () => { return ( <Router> <Layout> <Routes> <Route exact path="" element={<Home />} /> <Route exact path="login" element={<Login />} /> <Route exact path="register" element={<Register />} /> // These are the Private Components <Route exact path="/account" element={ <PrivateRoute> <Account /> </PrivateRoute> } /> <Route exact path="/quizzes" element={ <PrivateRoute> <Quizzes /> </PrivateRoute> } /> <Route exact path="/quizz/:quizzid" element={ <PrivateRoute> <Quizz /> </PrivateRoute> } /> <Route exact path="/admin/users" element={ <PrivateRoute> <Users /> </PrivateRoute> } /> <Route exact path="*" element={<NotFound />} /> </Routes> </Layout> </Router> ); };Esta es la ruta privada:
import { Navigate } from "react-router-dom"; import { useAuth } from "../auth/useAuth"; function PrivateRoute({ children }) { const auth = useAuth(); return auth.user ? children : <Navigate to="/login" />; } export default PrivateRoute;Podría usar el paquete auth-react-router https://www.npmjs.com/package/auth-react-router
Proporciona una API realmente simple para definir sus rutas y algunas configuraciones más (como rutas de redirección para rutas autorizadas y no autorizadas, componente alternativo para cada una de las rutas)
uso:
// routes.tsx import React from 'react'; import { IRoutesConfig } from 'auth-react-router'; import LoginPage from '../pages/LoginPage.tsx'; // public lazy loaded pages const LazyPublicPage = React.lazy(() => import('../pages/PublicPage.tsx')); // private lazy loaded pages const LazyPrivatePage = React.lazy(() => import('../pages/PrivatePage.tsx')); const LazyProfilePage = React.lazy(() => import('../pages/ProfilePage.tsx')); export const routes: IRoutesConfig = { publicRedirectRoute: '/profile', // redirect to `/profile` when authorized is trying to access public routes privateRedirectRoute: '/login', // redirect to `/login` when unauthorized user access a private route defaultFallback: <MyCustomSpinner />, public: [ { path: '/public', component: <LazyPublicPage />, }, { path: '/login', component: <LoginPage />, }, ], private: [ { path: '/private', component: <LazyPrivatePage />, }, { path: '/profile', component: <LazyProfilePage /> }, ], common: [ { path: '/', component: <p>common</p>, }, { path: '*', component: <p>page not found 404</p>, }, ], }; import { AppRouter, Routes } from 'auth-react-router'; import { BrowserRouter } from 'react-router-dom'; import { routes } from './routes'; export const App = () => { const { isAuth } = useAuthProvider(); return ( <BrowserRouter> <AppRouter isAuth={isAuth} routes={routes}> {/* Wrap `Routes` component into a Layout component or add Header */} <Routes /> </AppRouter> </BrowserRouter> ); };Al usar el atributo de replace , evitamos que el usuario use el botón Atrás del navegador.
PrivateRoute.js
import { Navigate } from 'react-router-dom'; const PrivateRoute = ({ currentUser, children, redirectTo }) => { if (!currentUser) return <Navigate to={redirectTo} replace />; return children; }; export default PrivateRoute;Implementación:
<Routes> <Route path='signIn' element={ <PrivateRoute currentUser={currentUser} redirectTo='/'> <SignInAndSignUpPage /> </PrivateRoute> } /> <Routes/>No sé si esta es la forma correcta de hacerlo, pero en realidad no necesita un componente de ruta privada. Simplemente puede poner todas las rutas privadas dentro de un componente y representarlo condicionalmente de la siguiente manera. En el siguiente código, puse todas las rutas privadas dentro del componente Privado y todas las rutas abiertas dentro del componente Público.
function RootRouter() { return ( <div> <Router> {isLoggedIn ? <PrivateRouter /> : <PublicRouter />} </Router> </div> ); } function PrivateRouter(props) { return ( <div> <ToastContainer autoClose={3000} hideProgressBar /> <NavBar /> <Routes> <Route path="/" exact element={<Home />} /> <Route path="/add" element={<Add />} /> <Route path="/update/:id" element={<Add />} /> <Route path="/view/:id" element={<Employee />} /> </Routes> </div> ); } function PublicRouter() { return ( <Routes> <Route path="/" element={<Login />} /> </Routes> ); }También puede usar casos de cambio para permitir el acceso según el rol del usuario.
Nota: no tiene que crear componentes separados, en realidad puede poner todas las rutas en un solo componente y renderizarlo usando las mismas condiciones.