React introduce el concepto de Hooks en la versión 16.8, permitiendo a los desarrolladores usar el estado y otras características de React en componentes funcionales. Los hooks personalizados son funciones de JavaScript que utilizan uno o más hooks de React estándar para compartir lógica entre componentes sin necesidad de usar clases. En este artículo, aprenderás cómo crear y utilizar hooks personalizados en tus aplicaciones React.
Un hook personalizado es una función en React que permite encapsular y reutilizar la lógica de estado o efectos en componentes funcionales. Al crear un hook personalizado, puedes extraer la lógica de un componente, lo que facilita su reutilización en otros componentes sin duplicar código.
Vamos a empezar creando un hook personalizado sencillo que maneje el estado de un contador. Este ejemplo te mostrará cómo encapsular la lógica de estado en un hook reutilizable.
Primero, creamos un archivo useContador.js
en el que definiremos nuestro hook personalizado:
import { useState } from 'react';
function useContador(inicial = 0) {
const [count, setCount] = useState(inicial);
const incrementar = () => setCount(count + 1);
const decrementar = () => setCount(count - 1);
const reiniciar = () => setCount(inicial);
return { count, incrementar, decrementar, reiniciar };
}
export default useContador;
En este ejemplo:
useContador
es nuestro hook personalizado que acepta un valor inicial (inicial
) y devuelve el estado del contador (count
) junto con funciones para incrementar, decrementar y reiniciar el contador.
useState
, que es un hook estándar de React, para manejar el estado interno del contador.
Ahora que hemos creado el hook useContador
, vamos a usarlo en un componente React:
import React from 'react';
import useContador from './useContador';
function Contador() {
const { count, incrementar, decrementar, reiniciar } = useContador(10);
return (
<div>
<h1>Contador: {count}</h1>
<button onClick={incrementar}>Incrementar</button>
<button onClick={decrementar}>Decrementar</button>
<button onClick={reiniciar}>Reiniciar</button>
</div>
);
}
export default Contador;
En este componente:
useContador(10)
inicializa el contador con un valor de 10.
count
y a las funciones incrementar
, decrementar
y reiniciar
desde el hook, que luego se utilizan en el renderizado y en los manejadores de eventos.
Los hooks personalizados también pueden encapsular efectos secundarios mediante useEffect
. Vamos a crear un hook que rastree el ancho de la ventana del navegador.
Creamos un nuevo archivo useAnchoVentana.js
para definir nuestro hook personalizado:
import { useState, useEffect } from 'react';
function useAnchoVentana() {
const [width, setWidth] = useState(window.innerWidth);
useEffect(() => {
const handleResize = () => setWidth(window.innerWidth);
window.addEventListener('resize', handleResize);
return () => {
window.removeEventListener('resize', handleResize);
};
}, []);
return width;
}
export default useAnchoVentana;
En este hook:
useState
se utiliza para almacenar el ancho actual de la ventana.
useEffect
registra un manejador de eventos para actualizar el ancho cada vez que la ventana se redimensiona. También limpia el evento cuando el componente se desmonta para evitar fugas de memoria.
Ahora podemos usar useAnchoVentana
en un componente:
import React from 'react';
import useAnchoVentana from './useAnchoVentana';
function MostrarAncho() {
const width = useAnchoVentana();
return <h1>Ancho de la ventana: {width}px</h1>;
}
export default MostrarAncho;
Este componente muestra el ancho actual de la ventana del navegador, que se actualiza dinámicamente gracias a nuestro hook personalizado.
Los hooks personalizados también pueden utilizar otros hooks como useRef
y useMemo
. A continuación, crearemos un hook que maneje un temporizador simple.
Crea un archivo useTemporizador.js
para el siguiente hook:
import { useRef, useState, useEffect } from 'react';
function useTemporizador(inicial = 0) {
const [tiempo, setTiempo] = useState(inicial);
const intervaloRef = useRef(null);
const iniciar = () => {
if (!intervaloRef.current) {
intervaloRef.current = setInterval(() => {
setTiempo((prev) => prev + 1);
}, 1000);
}
};
const detener = () => {
if (intervaloRef.current) {
clearInterval(intervaloRef.current);
intervaloRef.current = null;
}
};
const reiniciar = () => {
detener();
setTiempo(inicial);
};
useEffect(() => {
return () => detener(); // Limpia el intervalo al desmontar
}, []);
return { tiempo, iniciar, detener, reiniciar };
}
export default useTemporizador;
En este ejemplo:
useRef
se utiliza para almacenar una referencia mutable al intervalo de tiempo, permitiendo su gestión entre renders.
useEffect
asegura que el intervalo se limpia cuando el componente se desmonta.
Finalmente, vamos a utilizar useTemporizador
en un componente:
import React from 'react';
import useTemporizador from './useTemporizador';
function Temporizador() {
const { tiempo, iniciar, detener, reiniciar } = useTemporizador(0);
return (
<div>
<h1>Tiempo: {tiempo}s</h1>
<button onClick={iniciar}>Iniciar</button>
<button onClick={detener}>Detener</button>
<button onClick={reiniciar}>Reiniciar</button>
</div>
);
}
export default Temporizador;
Este componente implementa un temporizador simple que puede iniciar, detener y reiniciar usando el hook personalizado.
Los hooks personalizados pueden componerse entre sí para crear funcionalidades más complejas. Supongamos que queremos combinar useContador
y useTemporizador
.
Puedes crear un nuevo hook que combine los anteriores:
import useContador from './useContador';
import useTemporizador from './useTemporizador';
function useContadorTemporizado() {
const { count, incrementar, decrementar } = useContador();
const { tiempo, iniciar, detener, reiniciar } = useTemporizador();
return { count, incrementar, decrementar, tiempo, iniciar, detener, reiniciar };
}
export default useContadorTemporizado;
Ahora puedes usar este hook compuesto en un componente:
import React from 'react';
import useContadorTemporizado from './useContadorTemporizado';
function ContadorTemporizado() {
const { count, incrementar, decrementar, tiempo, iniciar, detener, reiniciar } = useContadorTemporizado();
return (
<div>
<h1>Contador: {count}</h1>
<button onClick={incrementar}>Incrementar</button>
<button onClick={decrementar}>Decrementar</button>
<h2>Tiempo: {tiempo}s</h2>
<button onClick={iniciar}>Iniciar Temporizador</button>
<button onClick={detener}>Detener Temporizador</button>
<button onClick={reiniciar}>Reiniciar Temporizador</button>
</div>
);
}
export default ContadorTemporizado;
Este componente ahora tiene la funcionalidad combinada de un contador y un temporizador, todo gestionado por un solo hook personalizado.
Los hooks personalizados en React son una herramienta poderosa para encapsular y reutilizar la lógica de estado, efectos, y más. Te permiten crear componentes más limpios, organizados y fáciles de mantener.
Jorge García
Fullstack developer