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 rehacemos DevJobs como una Single Page Application (SPA) usando React 19 y Vite. La aplicación vanilla del módulo anterior se transforma en un proyecto moderno con componentes funcionales, hooks de React, CSS Modules y un sistema de routing implementado desde cero con un custom hook — sin librerías externas de routing. Este módulo sienta las bases de la arquitectura de componentes que evolucionará en el módulo siguiente.

Lo que aprenderás

  • JSX: sintaxis de plantillas de React y diferencias con HTML (camelCase, className, etc.).
  • Componentes funcionales: creación de UI reutilizable encapsulada en funciones de JavaScript.
  • Hooks de React: useState, useEffect, useId, useRef aplicados a casos reales.
  • Custom hooks: extracción de lógica con estado a hooks propios (useRouter, useSearchForm).
  • Routing sin librerías externas: implementación de un router SPA usando window.history.pushState y el evento popstate.
  • CSS Modules: estilos con scope local a nivel de componente para evitar colisiones de nombres.

Estructura del Proyecto

02-react/src/
├── main.jsx                        # Punto de entrada; monta <App /> en el DOM
├── App.jsx                         # Componente raíz con rutas declaradas
├── index.css                       # Estilos globales
├── data.json                       # Datos de empleos de ejemplo
├── assets/
│   └── react.svg
├── hooks/
│   └── useRouter.jsx               # Custom hook de routing client-side
├── components/
│   ├── Header.jsx                  # Barra de navegación superior
│   ├── Footer.jsx                  # Pie de página con copyright
│   ├── JobCard.jsx                 # Tarjeta individual de oferta de empleo
│   ├── JobListings.jsx             # Contenedor del listado de tarjetas
│   ├── SearchFormSection.jsx       # Formulario de búsqueda con filtros y debounce
│   ├── Pagination.jsx              # Navegación entre páginas de resultados
│   ├── Pagination.module.css       # Estilos con scope del componente Pagination
│   ├── Route.jsx                   # Componente que renderiza según la ruta activa
│   └── Link.jsx                    # Enlace SPA que usa navigateTo del hook
└── pages/
    ├── Home.jsx                    # Página de inicio con hero y buscador
    ├── Search.jsx                  # Página de búsqueda y listado de empleos
    └── 404.jsx                     # Página de error 404

Custom Router Hook

El hook useRouter implementa el routing client-side sin dependencias externas. Escucha el evento nativo popstate del navegador para detectar cambios de URL y expone la función navigateTo para navegar de forma programática usando la History API.
import { useEffect, useState } from 'react'

export function useRouter() {
  const [currentPath, setCurrentPath] = useState(window.location.pathname)

  useEffect(() => {
    const handleLocationChange = () => {
      setCurrentPath(window.location.pathname)
    }

    window.addEventListener('popstate', handleLocationChange)
  }, [])

  function navigateTo(path) {
    // Actualiza la URL sin recargar la página
    window.history.pushState({}, '', path)
    // Dispara popstate manualmente (pushState no lo hace por sí solo)
    window.dispatchEvent(new PopStateEvent('popstate'))
  }

  return {
    currentPath,
    navigateTo
  }
}
pushState actualiza la URL en la barra del navegador pero no dispara el evento popstate. Por eso navigateTo despacha el evento manualmente con dispatchEvent, lo que hace que todos los componentes que usen useRouter se re-rendericen con la nueva ruta.
El hook se usa junto con los componentes Route y Link para construir el sistema de navegación completo:
// components/Route.jsx — renderiza el componente solo si la ruta coincide
import { useRouter } from "../hooks/useRouter";

export function Route ({ path, component: Component }) {
  const { currentPath } = useRouter()
  if (currentPath !== path) return null
  return <Component />
}

// components/Link.jsx — enlace que navega sin recargar la página
import { useRouter } from "../hooks/useRouter"

export function Link ({ href, children, ...restOfProps }) {
  const { navigateTo } = useRouter()

  const handleClick = (event) => {
    event.preventDefault()
    navigateTo(href)
  }

  return (
    <a href={href} {...restOfProps} onClick={handleClick}>
      {children}
    </a>
  )
}
El componente raíz App.jsx define todas las rutas de la aplicación de forma declarativa:
import { Header } from './components/Header.jsx'
import { Footer } from './components/Footer.jsx'
import { HomePage } from './pages/Home.jsx'
import { SearchPage } from './pages/Search.jsx'
import { Route } from './components/Route.jsx'

function App() {
  return (
    <>
      <Header />
      <Route path="/"       component={HomePage}   />
      <Route path="/search" component={SearchPage} />
      <Footer />
    </>
  )
}

export default App

Componentes Principales

Header

Barra de navegación superior con el logotipo SVG de DevJobs como <Link> y los enlaces de navegación. Incluye un enlace convencional (<a>) junto a un <Link> SPA para comparar ambos comportamientos.

Footer

Pie de página simple con el texto de copyright. Componente stateless de presentación pura.

JobCard

Tarjeta de oferta de empleo. Gestiona el estado de isApplied con useState: al pulsar “Aplicar” el botón cambia a “Aplicado” y se deshabilita visualmente. Recibe el objeto job como prop.

JobListings

Contenedor que recibe un array jobs y renderiza un <JobCard> por cada elemento. Muestra un mensaje cuando no hay resultados que coincidan con los filtros activos.

SearchFormSection

Formulario de búsqueda avanzada con tres selectores (tecnología, ubicación, nivel de experiencia) y un input de texto con debounce de 500 ms. Extrae su lógica al hook interno useSearchForm.

Pagination

Navegación entre páginas de resultados. Genera las URLs con URLSearchParams para que cada página sea enlazable. Deshabilita los botones anterior/siguiente en los extremos.

Route

Componente de routing condicional: compara currentPath (del hook useRouter) con su prop path y renderiza el componente asociado únicamente cuando coinciden.

Link

Versión SPA del elemento <a>: intercepta el click, llama a navigateTo del hook useRouter y evita la recarga de página mientras mantiene la URL accesible en el href.

Scripts disponibles

Los siguientes scripts están definidos en package.json:
ScriptComandoDescripción
devviteInicia el servidor de desarrollo con HMR en localhost:5173
buildvite buildGenera el bundle de producción optimizado en dist/
linteslint .Analiza el código con ESLint y las reglas de React Hooks
previewvite previewSirve el bundle de producción localmente para previsualizar

Cómo Ejecutar

cd 02-react
npm install
npm run dev
La aplicación estará disponible en http://localhost:5173. Vite proporciona Hot Module Replacement (HMR), por lo que los cambios en los componentes se reflejan instantáneamente sin perder el estado de la aplicación.
Si quieres explorar React sin ningún paso de compilación, el repositorio incluye la carpeta 02-react-cdn-version/ donde la misma aplicación se carga desde un CDN usando importmap y JSX compilado en el navegador con Babel Standalone. Es ideal para entender los fundamentos antes de introducir herramientas de build.

Build docs developers (and LLMs) love