Creando custom hook para formulario

Introducción del problema

Ahora mismo tenemos un componente principal que gestiona toda la lógica de búsqueda, y eso lo hace demasiado complejo. El componente tiene mezclado:

  • handleSubmit
  • handleTextChange
  • La lógica de búsqueda (search)
  • IDs como tecnología, localización, experiencia, etc.

Todo esto junto hace que el componente sea difícil de leer y mantener. Vamos a separar responsabilidades usando un custom hook.

Identificando la complejidad

Cuando un componente tiene demasiadas responsabilidades, es señal de que necesitamos refactorizar. En nuestro caso, la lógica del formulario de búsqueda debería vivir en su propio hook personalizado.

Creando useSearchForm

Vamos a crear un custom hook llamado useSearchForm que encapsule toda la lógica del formulario.

Los custom hooks, a diferencia de hooks como el router que vimos en el vídeo anterior, pueden recibir parámetros igual que funciones normales.

Parámetros del hook

Nuestro hook necesita recibir:

  • onSearch: función que se ejecuta al buscar
  • onTextFilter: función para filtrar por texto
  • IDs necesarias para los filtros:
    • IDTechnology
    • IDLocation
    • IDExperienceLevel
    • Cualquier otra ID que use el componente
function useSearchForm({ onSearch, onTextFilter, IDTechnology, IDLocation, IDExperienceLevel }) {
  // Lógica del hook aquí
}

Añadiendo estado interno

El texto de búsqueda necesita estar en un estado. Lo añadimos dentro del custom hook:

function useSearchForm({ onSearch, onTextFilter, IDTechnology, IDLocation, IDExperienceLevel }) {
  const [searchText, setSearchText] = useState('')

  // Este estado:
  // - guarda el texto introducido por el usuario
  // - se actualiza en handleChange
  // - será devuelto al componente para mostrarlo en el input
}

Implementando los handlers

Ahora movemos la lógica de los handlers al hook:

function useSearchForm({ onSearch, onTextFilter, IDTechnology, IDLocation, IDExperienceLevel }) {
  const [searchText, setSearchText] = useState('')

  const handleSubmit = (event) => {
    event.preventDefault()

    const formData = new FormData(event.target)

    const filters = {
      text: formData.get('search'),
      technology: formData.get('technology'),
      location: formData.get('location'),
      experience: formData.get('experience'),
    }

    onSearch(filters)
  }

  const handleChange = (event) => {
    const text = event.target.value
    setSearchText(text)
    onTextFilter(text)
  }

  // Devolvemos lo que el componente necesita
  return {
    searchText,
    handleSubmit,
    handleChange,
  }
}

Usando el hook en el componente

Ahora nuestro componente se simplifica enormemente:

function SearchComponent() {
  const { searchText, handleSubmit, handleChange } = useSearchForm({
    onSearch: performSearch,
    onTextFilter: filterByText,
    IDTechnology: 'tech',
    IDLocation: 'loc',
    IDExperienceLevel: 'exp',
  })

  return (
    <form onSubmit={handleSubmit}>
      <input value={searchText} onChange={handleChange} name="search" />
      {/* Resto del formulario */}
    </form>
  )
}

El componente ya no implementa la lógica, solo consume las funciones y valores del hook.

Ventajas de esta refactorización

Código más limpio: el componente se enfoca en renderizar

Lógica reutilizable: el hook puede usarse en otros componentes

Más fácil de testear: podemos probar el hook independientemente

Mejor organización: separación clara de responsabilidades

Reglas importantes sobre custom hooks

1. Llamar solo en el nivel superior

Los custom hooks deben llamarse solo en el nivel superior del componente o de otro custom hook.

NO dentro de:

  • Callbacks (handleClick, handleSubmit, etc.)
  • Condicionales (if)
  • Loops (for, while)
  • useEffect ni otros hooks
// ❌ MAL - dentro de un callback
function Component() {
  const handleClick = () => {
    const { data } = useSearchForm() // ERROR
  }
}

// ❌ MAL - dentro de un condicional
function Component() {
  if (condition) {
    const { data } = useSearchForm() // ERROR
  }
}

// ✅ BIEN - nivel superior
function Component() {
  const { searchText, handleSubmit } = useSearchForm()

  const handleClick = () => {
    // Usar los valores del hook aquí está bien
    console.log(searchText)
  }
}

2. Deben empezar con el prefijo “use”

Como useState, useEffect, etc.

Esto no es solo una convención: React confía en ese nombre para validar reglas internas.

// ✅ BIEN
function useSearchForm() {}
function useUserData() {}
function useAuthentication() {}

// ❌ MAL
function searchFormHook() {} // No empieza con "use"
function getSearchForm() {} // No es un hook

3. Solo se pueden usar en componentes o custom hooks

Los custom hooks solo se pueden usar en:

  • Componentes de React
  • Otros custom hooks

NO en funciones normales fuera del ecosistema de React:

// ❌ MAL - función normal
function calculateTotal() {
  const { data } = useData() // ERROR
  return data.total
}

// ✅ BIEN - componente
function Component() {
  const { data } = useData()
  return <div>{data.total}</div>
}

// ✅ BIEN - otro custom hook
function useCalculateTotal() {
  const { data } = useData()
  return data.total
}

Verificando que todo funciona

Después de la refactorización, todo debe seguir funcionando exactamente igual que antes. La diferencia está en la organización del código, no en el comportamiento.

Conclusión

Extraer la lógica a custom hooks es una práctica esencial en React moderno:

  • El componente se enfoca en renderizar
  • La lógica vive en un sitio reutilizable
  • El desarrollo se vuelve más escalable

Esta técnica mantiene tu código:

  • ✅ Limpio
  • ✅ Modular
  • ✅ Fácil de testear
  • ✅ Mantenible a largo plazo

Usar custom hooks correctamente es señal de código React profesional y bien arquitecturado.