Guía de Implementación: Sistema de Autenticación con React Router
Esta guía documenta los cambios realizados para trabajar con React Router. Puedes partir usando este repositorio: https://github.com/Kelocoes/dmi-20252/tree/week-10/react-router
Implementación Paso a Paso
Paso 1: Crear el Context de Autenticación
Crear el archivo src/layout/Auth/Auth.tsx:
import {
createContext,
useContext,
useEffect,
useState,
type ReactNode,
} from "react";
import { Outlet } from "react-router";
type User = {
id: string;
username?: string;
email: string;
password?: string;
};
type AuthContextType = {
user: User | null;
isAuthenticated: boolean;
login: (_email: string, _password: string) => void;
register: (_username: string, _email: string, _password: string) => void;
isLoading: boolean;
};
const AuthContext = createContext<AuthContextType | undefined>(undefined);
const AuthProvider = ({ children }: { children: ReactNode }) => {
const [user, setUser] = useState<User | null>(null);
const [isLoading, setIsLoading] = useState(true);
const login = (email: string, password: string) => {
// Simulación de login
const userData = { id: "1", email, password };
setUser(userData);
localStorage.setItem("user", JSON.stringify(userData));
};
const register = (username: string, email: string, password: string) => {
// Simulación de registro
const userData = { id: "1", username, email, password };
setUser(userData);
localStorage.setItem("user", JSON.stringify(userData));
};
useEffect(() => {
const storedUser = localStorage.getItem("user");
if (storedUser) {
try {
setUser(JSON.parse(storedUser));
} catch (error) {
console.error("Error parsing stored user:", error);
localStorage.removeItem("user");
setUser(null);
}
} else {
setUser(null);
}
setIsLoading(false);
}, []);
const isAuthenticated = user !== null;
return (
<AuthContext.Provider
value={{ user, isAuthenticated, login, register, isLoading }}
>
{children}
</AuthContext.Provider>
);
};
export const useAuth = () => {
const context = useContext(AuthContext);
if (!context) throw new Error("useAuth must be used within AuthProvider");
return context;
};
export default function Auth() {
return (
<AuthProvider>
<div id="auth-layout">
<h1>Auth Layout</h1>
<Outlet />
</div>
</AuthProvider>
);
}
Características clave:
- Context API: Para compartir estado entre componentes
- LocalStorage: Persistencia de sesión del usuario
- Estado de carga: Evita redirecciones prematuras
- Provider Pattern: Envuelve los componentes hijos
Paso 2: Configurar el Router
Crear el archivo src/router/Router.tsx:
import { createBrowserRouter, Navigate } from "react-router";
import Landing from "../pages/landing/Landing";
import Register from "../pages/register/Register";
import AuthLayout from "../layout/Auth/Auth";
import Login from "../pages/login/Login";
import Feed from "../pages/feed/Feed";
const router = createBrowserRouter(
[
{
path: "/",
Component: AuthLayout,
children: [
{
index: true,
element: <Navigate to="login" replace />,
},
{ path: "login", Component: Login },
{ path: "register", Component: Register },
{ path: "feed", Component: Feed },
{ path: "*", Component: Landing },
],
},
],
{ basename: "/dmi" }
);
export default router;
Configuración importante:
- Rutas anidadas: Todas bajo el
AuthLayout - Basename: Configurado para
/dmi - Redirección por defecto: Redirige a login desde la raíz
Paso 3: Crear las Páginas
Página de Login (src/pages/login/Login.tsx)
import { useState } from "react";
import { useNavigate, Link } from "react-router";
import { useAuth } from "../../layout/Auth/Auth";
export default function Login() {
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const { login } = useAuth();
const navigate = useNavigate();
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
login(email, password);
navigate("/feed");
};
return (
<div id="login-page">
<h2>Login</h2>
<form onSubmit={handleSubmit}>
<div>
<label>Email:</label>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
required
/>
</div>
<div>
<label>Password:</label>
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
required
/>
</div>
<button type="submit">Login</button>
</form>
<p>
Don't have an account? <Link to="/register">Register</Link>
</p>
</div>
);
}
Página de Registro (src/pages/register/Register.tsx)
import { useState } from "react";
import { useNavigate, Link } from "react-router";
import { useAuth } from "../../layout/Auth/Auth";
export default function Register() {
const [username, setUsername] = useState("");
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const { register } = useAuth();
const navigate = useNavigate();
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
register(username, email, password);
navigate("/feed");
};
return (
<div id="register-page">
<h2>Register</h2>
<form onSubmit={handleSubmit}>
<div>
<label>Username:</label>
<input
type="text"
value={username}
onChange={(e) => setUsername(e.target.value)}
required
/>
</div>
<div>
<label>Email:</label>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
required
/>
</div>
<div>
<label>Password:</label>
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
required
/>
</div>
<button type="submit">Register</button>
</form>
<p>
Already have an account? <Link to="/login">Login</Link>
</p>
</div>
);
}
Página Protegida - Feed (src/pages/feed/Feed.tsx)
import { Navigate } from "react-router";
import { useAuth } from "../../layout/Auth/Auth";
export default function Feed() {
const { user, isAuthenticated, isLoading } = useAuth();
if (isLoading) {
return <div>Loading...</div>;
}
if (!isAuthenticated) {
return <Navigate to="/login" replace />;
}
return (
<div id="feed-page">
<h1>Feed Page</h1>
<p>Welcome, {user?.username || user?.email}!</p>
</div>
);
}
Protección de ruta:
- Estado de carga: Espera verificación de autenticación
- Redirección condicional: Solo redirige si no está autenticado
- Información del usuario: Muestra datos del usuario logueado
Paso 4: Integrar el Router en la Aplicación
Modificar src/main.tsx:
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import { RouterProvider } from "react-router";
import "./index.css";
import router from "./router/Router.tsx";
createRoot(document.getElementById("root")!).render(
<StrictMode>
<RouterProvider router={router} />
</StrictMode>
);
Características del Sistema de Autenticación
1. Persistencia de Sesión
- Los datos del usuario se guardan en
localStorage - Se restaura automáticamente al recargar la página
- Manejo de errores al parsear datos corruptos
2. Estados de Carga
- Previene redirecciones prematuras
- Muestra indicador de carga mientras verifica autenticación
- Mejora la experiencia del usuario
3. Navegación Protegida
- Redirige a login si no está autenticado
- Mantiene la información del usuario entre páginas
- Uso de
replacepara evitar loops de navegación
4. Context Pattern
- Estado global accesible desde cualquier componente
- Funciones de login/register centralizadas
- Verificación de autenticación consistente
Manejo de Redirecciones
// ✅ Uso correcto de Navigate con replace
if (!isAuthenticated) {
return <Navigate to="/login" replace />;
}
// ✅ Esperar estado de carga antes de decidir
if (isLoading) {
return <div>Loading...</div>;
}
Estructura Final del Proyecto
src/
├── layout/
│ └── Auth/
│ └── Auth.tsx # Context y Provider de autenticación
├── pages/
│ ├── feed/
│ │ └── Feed.tsx # Página protegida
│ ├── landing/
│ │ └── Landing.tsx # Página pública
│ ├── login/
│ │ └── Login.tsx # Formulario de login
│ └── register/
│ └── Register.tsx # Formulario de registro
├── router/
│ └── Router.tsx # Configuración de rutas
└── main.tsx # Punto de entrada con RouterProvider
Cómo Usar
- Acceder a la aplicación: Va automáticamente a
/login - Registrarse: Crear cuenta nueva y se redirige a
/feed - Iniciar sesión: Usar credenciales y acceder al feed
- Persistencia: Los datos se mantienen al recargar la página
- Protección: Intentar acceder a
/feedsin login redirige a/login
Esta implementación proporciona una base sólida para un sistema de autenticación escalable y mantenible.