Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/midudev/jscamp/llms.txt

Use this file to discover all available pages before exploring further.

En este módulo evolucionamos la SPA de React hacia una aplicación de producción real. Sustituimos el custom hook de routing por React Router v7, introducimos Zustand para gestionar el estado global de autenticación y favoritos, y añadimos rutas protegidas que redirigen al usuario al login si no ha iniciado sesión. El resultado es una app con flujo completo de autenticación (login, registro, perfil) y la capacidad de guardar empleos favoritos.

Lo que aprenderás

  • React Router v7: configuración de rutas con <BrowserRouter>, <Routes> y <Route>, navegación programática con useNavigate, parámetros de URL con useParams y query strings con useSearchParams.
  • Zustand stores: creación de stores minimalistas con create() para manejar estado global sin boilerplate.
  • Context API: uso de React.createContext y providers como alternativa o complemento a Zustand.
  • Rutas protegidas: componente ProtectedRoute que redirige a /login usando <Navigate> cuando el usuario no está autenticado.
  • Páginas de login y registro: formularios controlados con useId para accesibilidad, validación y autenticación mock.
  • Gestión de favoritos: store de Zustand con acciones para añadir, eliminar y verificar empleos favoritos.

Nuevas Dependencias

Las siguientes dependencias se añaden respecto al módulo anterior:
PaqueteVersiónPropósito
react-router^7.9.6Routing declarativo con soporte para loaders, actions y SSR
zustand^5.0.8Gestión de estado global minimalista basada en hooks
snarkdown^2.0.0Conversor ligero de Markdown a HTML para renderizar descripciones de empleos

Zustand: authStore

El store de autenticación (authStore.js) gestiona un único booleano isLoggedIn con dos acciones: login y logout. Su sencillez lo convierte en el ejemplo perfecto para introducir Zustand.
import { create } from 'zustand'

export const useAuthStore = create((set) => ({
  // Estado
  isLoggedIn: false,

  // Acciones
  login:  () => set({ isLoggedIn: true }),
  logout: () => set({ isLoggedIn: false })
}))
Cualquier componente puede suscribirse al store con una sola línea:
const { isLoggedIn, login, logout } = useAuthStore()
Zustand re-renderiza únicamente los componentes que lean los valores que han cambiado, sin necesidad de Context ni useReducer.

Zustand: favoritesStore

El store de favoritos es más completo: mantiene un array de IDs de empleos y expone varias acciones para manipularlo de forma inmutable.
import { create } from 'zustand'

export const useFavoritesStore = create((set, get) => ({
  // Estado
  favorites: [],

  clearFavorites: () => {
    set({ favorites: [] })
  },

  // Acciones
  addFavorite: (jobId) => {
    set((state) => ({
      favorites: state.favorites.includes(jobId)
        ? state.favorites
        : [...state.favorites, jobId]
    }))
  },

  removeFavorite: (jobId) => {
    set((state) => ({
      favorites: state.favorites.filter((id) => id !== jobId)
    }))
  },

  isFavorite: (jobId) => {
    return get().favorites.includes(jobId)
  },

  toggleFavorite: (jobId) => {
    const { addFavorite, removeFavorite, isFavorite } = get()
    const isFav = isFavorite(jobId)
    isFav ? removeFavorite(jobId) : addFavorite(jobId)
  },

  countFavorites: () => get().favorites.length
}))
La función get() recibida como segundo argumento de create permite leer el estado actual dentro de las propias acciones del store, sin necesidad de subscribirse como componente. Esto es muy útil para acciones que dependen de otros valores del store, como toggleFavorite.

Rutas Protegidas

El componente ProtectedRoute actúa como guardia de rutas privadas. Lee el estado de autenticación desde useAuthStore y, si isLoggedIn es false, redirige automáticamente al usuario a la ruta /login usando el componente <Navigate> de React Router. Si el usuario está autenticado, renderiza el contenido hijo tal cual.
import { Navigate } from "react-router";
import { useAuthStore } from "../store/authStore";

export function ProtectedRoute({ children, redirectTo = '/login' }) {
  const { isLoggedIn } = useAuthStore()

  if (!isLoggedIn) {
    return <Navigate to={redirectTo} replace />
  }

  return children
}
Para proteger una ruta en App.jsx basta con envolver el componente de página:
<Route
  path="/profile"
  element={
    <ProtectedRoute>
      <ProfilePage />
    </ProtectedRoute>
  }
/>
Esta implementación de autenticación es un mock para fines didácticos. El estado de isLoggedIn se pierde al recargar la página porque no está persistido. En una aplicación real deberías integrar un proveedor de autenticación (JWT, sesiones, OAuth) y persistir el token de forma segura.

Nuevas Páginas

Este módulo introduce cuatro páginas nuevas respecto al módulo anterior:

Detail

Página de detalle de una oferta de empleo. Obtiene el jobId de los parámetros de URL con useParams y hace fetch a https://jscamp-api.vercel.app/api/jobs/:id. Renderiza la descripción en Markdown usando snarkdown. Muestra el botón “Aplicar ahora” solo si el usuario está autenticado y permite marcar el empleo como favorito con el corazón.

Login

Formulario de inicio de sesión con campos de email y contraseña. Usa useId para generar IDs accesibles dinámicamente. Al enviar, llama a useAuthStore().login() y redirige a /search con useNavigate. La validación es mock (cualquier email+contraseña válidos funcionan).

ProfilePage

Página de perfil del usuario autenticado (ruta protegida). Muestra información de perfil demo (nombre, email, experiencia laboral, habilidades). Incluye el botón “Cerrar Sesión” que llama a useAuthStore().logout().

Register

Formulario de registro con campos de nombre, email y contraseña. Al completarse, llama a login() del store para autenticar inmediatamente al nuevo usuario y redirige a /search. Al igual que Login, usa useId para los atributos id/name de los inputs.

Cómo Ejecutar

cd 03-router-and-zustand
npm install
npm run dev
La aplicación estará disponible en http://localhost:5173. Puedes navegar a /login o /register para probar el flujo de autenticación. Una vez autenticado, accede a /profile (ruta protegida) o navega al detalle de cualquier empleo desde la página de búsqueda para ver el botón de favoritos.

Build docs developers (and LLMs) love