En esta clase vamos a integrar correctamente los filtros dentro del flujo de búsqueda de ofertas de trabajo, haciendo que la petición a la API se actualice cada vez que el usuario cambia un filtro (tecnología, texto, tipo, experiencia, etc).

El problema inicial

Aunque la aplicación ya hace una llamada HTTP a la API, hay un problema: no está usando los filtros seleccionados por el usuario.

Los filtros se actualizan en el estado correctamente, pero esa información no se envía en la petición a la API.

Objetivo

Cuando alguien escribe “JavaScript” o cambia cualquier filtro, la app debe volver a consultar la API, enviando esos filtros como parámetros.

Estado de los filtros

El estado de los filtros ya existe y funciona correctamente:

  • Se pasa al formulario de búsqueda
  • Se actualiza cuando el usuario interactúa con los filtros
  • No hace falta tocar esa parte

El único problema es que la petición a la API no lee esos filtros antes de ejecutarse.

Cómo enviar filtros a la API

La API acepta filtros en la URL mediante query params, por ejemplo:

?text=react&limit=10&offset=0

La API responde correctamente a parámetros como:

  • text - Búsqueda por texto
  • technology - Filtro por tecnología (javascript, python, etc.)
  • type - Filtro por tipo/ubicación de trabajo (remote, onsite, hybrid)
  • level - Filtro por nivel de experiencia (junior, mid, senior)

El siguiente paso es construir esos parámetros dinámicamente.

Construcción de los query params

Para construir los parámetros de la URL, utilizamos URLSearchParams, que forma parte del estándar de la plataforma web.

Implementación paso a paso

const params = new URLSearchParams()

// Si existe texto de búsqueda
if (textToFilter) {
  params.append('text', textToFilter)
}

// Si existe filtro de tecnología
if (filters.technology) {
  params.append('technology', filters.technology)
}

// Si existe filtro de ubicación/tipo
if (filters.location) {
  params.append('type', filters.location)
}

// Si existe filtro de nivel de experiencia
if (filters.experienceLevel) {
  params.append('level', filters.experienceLevel)
}

// Convertir a string y concatenar a la URL
const queryParams = params.toString()
const url = `https://jscamp-api.vercel.app/api/jobs?${queryParams}`

Ventaja de este enfoque

Si no hay filtros activos, searchParams.toString() devuelve una cadena vacía, y la URL queda simplemente con un ? al final, lo cual funciona perfectamente.

Primer intento y error detectado

Al hacer una prueba en la página:

  1. Refrescas
  2. Seleccionas tecnología → JavaScript
  3. No funciona

¿Por qué no funciona?

El useEffect que hace la petición solo se ejecuta una vez, porque su array de dependencias está vacío:

useEffect(() => {
  // Petición a la API
}, []) // ❌ Array vacío = solo se ejecuta al montar el componente

También aparece una advertencia del linter:

El hook debería listar sus dependencias o eliminar el array (pero si lo quitas, generas un loop infinito porque se re-renderiza sin parar).

La solución correcta

Las dependencias que deben hacer que el efecto se ejecute son:

  • textToFilter - El texto de búsqueda
  • filters.technology - Filtro de tecnología
  • filters.location - Filtro de ubicación/tipo
  • filters.experienceLevel - Filtro de nivel de experiencia

Añadiendo las dependencias

useEffect(() => {
  const params = new URLSearchParams()

  if (textToFilter) {
    params.append('text', textToFilter)
  }

  if (filters.technology) {
    params.append('technology', filters.technology)
  }

  if (filters.location) {
    params.append('type', filters.location)
  }

  if (filters.experienceLevel) {
    params.append('level', filters.experienceLevel)
  }

  const queryParams = params.toString()

  fetch(`https://jscamp-api.vercel.app/api/jobs?${queryParams}`)
    .then((res) => res.json())
    .then((data) => setJobs(data))
}, [textToFilter, filters.technology, filters.location, filters.experienceLevel]) // ✅ Dependencias correctas

Ahora el efecto se ejecutará cada vez que cambie alguno de estos valores.

Comprobación

Haciendo pruebas en directo:

Búsqueda por texto

  • Buscas “Python” → ✅ Funciona
  • Buscas “JavaScript” → ✅ Funciona

Combinación de filtros

JavaScript + remoto → ✅ La API recibe ambos filtros y devuelve resultados correctamente

En la interfaz aparecen los filtros activos:

  • Technology: JavaScript
  • Type: Remote

Caso sin resultados

Para mejorar la experiencia de usuario, añadimos un mensaje cuando no hay resultados:

{
  jobs.length === 0 ? (
    <p
      style={{
        padding: '2rem',
        textWrap: 'balance',
      }}
    >
      No se han encontrado empleos que coincidan con los criterios de búsqueda.
    </p>
  ) : (
    <JobsList jobs={jobs} />
  )
}

Mejoras aplicadas

  • Padding para mejor presentación
  • text-wrap: balance para que el texto se distribuya mejor

Todo funcionando

Ejemplo final con múltiples filtros:

  • Texto: “JavaScript”
  • Tipo: Remoto
  • Nivel: Junior

La búsqueda devuelve el resultado correcto, filtrando por todos los criterios seleccionados.

Resumen final

En esta clase has aprendido a:

  1. ✅ Identificar por qué los filtros no funcionaban inicialmente
  2. ✅ Usar URLSearchParams para construir query params dinámicamente
  3. ✅ Añadir dependencias correctas al useEffect
  4. ✅ Integrar los filtros en la petición HTTP
  5. ✅ Manejar el caso de búsquedas sin resultados
  6. ✅ Probar que todo funciona con múltiples combinaciones de filtros

Ahora el sistema de filtros está completamente integrado y funcional. Cada vez que el usuario cambia un filtro, la aplicación hace una nueva petición a la API con los parámetros actualizados.

Pero todavía no tenemos paginación. ¡Vamos a ello!