Componente Protected
Un componente que restringe el acceso a sus hijos (UI) según el estado de autenticación de la persona usuaria. Si la persona usuaria no está autenticada, redirige a la página establecida, de lo contrario renderiza sus hijos. Se utiliza dentro del archivo donde se definen las rutas, que puede ser el mismo donde se define el componente enrutador. Se recomienda que este archivo sea MichiRouter.tsx.
Toma una prop y un children. La prop es un objeto de configuración con las siguientes propiedades:
- states (obligatorio):
Record<string, any>objeto con{ user: any, isLoading: boolean }. Por ejemplo el valor retornado por un hook de autenticación (Zustand, Redux, Context, etc.). Se puede pasar directamente el store si este retorna un objeto con esas dos propiedades.- user:
any→ el objeto o valor del usuario actual. Si esnulloundefined, se considera que no hay usuario autenticado. - isLoading:
boolean→ indica si el proceso de autenticación/carga está en curso.
- user:
- redirectionPath (obligatorio):
stringruta destino cuando no hay usuario autenticado. Por defecto/. - loadingComponent (opcional):
JSX.Elementcomponente React a mostrar mientrasisLoadingestrue. - defaultMessage (opcional):
string. Mensaje por defecto a mostrar si no se provee loadingComponent. Si no se provee ningún mensaje, no se muestra nada.
El children es el contenido que se renderizará si la persona usuaria está autenticada.
- children (obligatorio):
ReactNodecontenido protegido.
import React, { useEffect } from "react";import { useNavigate } from "./Michi-router";import { ProtectedProps } from "./types";
/** * Un componente que restringe el acceso a sus hijos según el estado de autenticación del usuario. * * Si el usuario no está autenticado, redirige a la página de landing ("/"). * De lo contrario, renderiza sus hijos. * * @param {Object} props - Props del componente * @param {React.ReactNode} props.children - Los nodos React a renderizar si el usuario está autenticado. * @returns {JSX.Element|null} Los hijos si está autenticado, de lo contrario loader. */export default function Protected({ children, configObject,}: ProtectedProps): JSX.Element | null { const navigate = useNavigate();
/** * Objeto de configuración para el manejo de rutas protegidas. * * @property {Object} states - Contiene el estado actual del usuario y el estado de carga. * @property {any} states.user - El objeto o valor del usuario actual. * @property {boolean} states.isLoading - Indica si el proceso de autenticación/carga está en curso. * @property {string} redirectionPath - Ruta a la que se redirige si el usuario no está autenticado. * @property {React.ReactNode} [loadingComponent] - Componente personalizado opcional para mostrar mientras carga. * @property {string} [defaultMessage] - Mensaje por defecto a mostrar si no se provee loadingComponent. Si no se provee ningún mensaje, no se muestra nada. */ const config: { states: { user: any; isLoading: boolean }; redirectionPath: string; loadingComponent?: React.ReactNode; defaultMessage?: string; } = { states: configObject?.states || null, redirectionPath: configObject?.redirectionPath || "/", loadingComponent: configObject?.loadingComponent || null, defaultMessage: configObject?.defaultMessage || undefined, };
if (!config.states) { console.error( "Componente Protected: El objeto de configuración es inválido. Este es el formato esperado:\n{\n states: { user: any; isLoading: boolean };\n redirectionPath: string;\n loadingComponent?: React.ReactNode;\n defaultMessage?: string;\n}" ); return null; } // Leemos el estado directamente desde el store const { user, isLoading } = config.states;
useEffect(() => { // Redirigir solo cuando haya terminado de cargar y el usuario NO esté autenticado if (!isLoading && !user) { navigate(config.redirectionPath); } }, [isLoading, user, navigate, config.redirectionPath]);
// Mientras carga, mostrar loadingComponent si está definido, si no y defaultMessage true mostrar texto if (isLoading) { if (config.loadingComponent) return config.loadingComponent as JSX.Element; return config.defaultMessage ? <>{config.defaultMessage}</> : null; }
return user ? children : null;}Ejemplo de uso
Sección titulada «Ejemplo de uso»// ...otros importsimport { Protected, RouterProvider as MichiProvider,} from "@arielgonzaguer/michi-router";import useAuthStore from "../store/useAuthStore"; // la store que maneja autenticaciónimport Login from "../paginas/Login.jsx";import Home from "../paginas/Home.jsx";import Notas from "../paginas/Notas.jsx";// ...otros componentes
export default function MichiRouter() { const configObject = { states: useAuthStore(), redirectionPath: "/", loadingComponent: ( <div className="w-full h-screen flex items-center justify-center"> Cargando... </div> ), defaultMessage: "Cargando autenticación...", };
const routes = [ { path: "/", component: <Home /> }, { path: "/login", component: <Login />, }, { path: "/notas", component: ( <Protected configObject={configObject}> <Notas /> </Protected> ), }, // ...otras rutas ];
return ( <RouterProvider routes={rutas} layout={BaseLayout}> <NotFoud404 /> </RouterProvider> );}Recomendaciones para usar
Sección titulada «Recomendaciones para usar »El componente
Requisitos previos
Sección titulada «Requisitos previos»- Tener una store de autenticación. La store debe proveer:
-
user:null | undefined | objectundefined→ aún no sabemos si hay usuario (fase de carga inicial).null→ no hay usuario autenticado.object→ usuario autenticado.
-
isLoading:booleantrue→ la app está verificando la sesión.false→ ya se verificó (haya o no usuario).
- Haber implementado un listener de autenticación (por ejemplo, onAuthStateChanged en Firebase) que actualice la store antes de montar el router.
Buenas prácticas recomendadas
Sección titulada «Buenas prácticas recomendadas»- Siempre inicialice
isLoadingentruehasta que se termine de verificar la sesión. Así se evitan redirecciones prematuras y el efecto “parpadeo”. - Use un
loadingComponentpara personalizar la experiencia.
Ejemplo con spinner:
<Protected configObject={{ states: { user, isLoading }, redirectionPath: "/", loadingComponent: <Spinner />, }}> <PanelPrivado /></Protected>- Evite hacer side-effects dentro de
Protected. El componente solo debería redirigir o mostrar loader, no cargar datos ni mutar estado global.
Ejemplo de reglas mínimas en Firestore
Sección titulada «Ejemplo de reglas mínimas en Firestore»rules_version = '2';service cloud.firestore { match /databases/{database}/documents{ // Permitir acceso al documento solo a usuarios autenticados match /emergenciaData/casa { allow read, write: if request.auth != null; }
// Denegar todo lo demás match /{document=**} { allow read, write: if false; } }}