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:
handleSubmithandleTextChange- 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 buscaronTextFilter: función para filtrar por texto- IDs necesarias para los filtros:
IDTechnologyIDLocationIDExperienceLevel- 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) useEffectni 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.