useCallback es un hook de React que devuelve una versión memoizada de una función que no cambia entre renderizados hasta que sus dependencias cambien. A diferencia de una declaración de función normal dentro de un componente (que crea una nueva función en cada renderizado), useCallback estabiliza la referencia a la función, evitando rerenderizados innecesarios de componentes hijos optimizados con React.memo. Según React Documentation (2025), useCallback solo es útil en combinación con React.memo o hooks que dependen de una referencia estable.
Puntos clave
useCallback es un hook añadido en React 16.8 que memoiza una función: devuelve la misma referencia hasta que las dependencias cambien. Sin useCallback, cada declaración de función dentro de un componente crea un nuevo objeto de función en cada renderizado. Para primitivas esto es imperceptible, pero al pasar estos callbacks a componentes hijos optimizados con React.memo, cada nueva referencia provoca un rerenderizado del componente hijo.
Sintácticamente, useCallback es equivalente a useMemo para una función: useCallback(fn, deps) es una abreviatura de useMemo(() => fn, deps). React almacena la función memoizada en el almacenamiento interno del nodo fiber y compara las dependencias en cada renderizado. Si las dependencias no han cambiado (Object.is para cada elemento), se devuelve la función anterior.
Según React Documentation — useCallback (2025), no debes envolver cada función en useCallback. El hook tiene su coste: llamar al hook, comparar dependencias y asignar memoria para el array de dependencias. Si un componente es simple y no tiene árboles profundos con React.memo, useCallback solo ralentizará la aplicación. La optimización debe ser medible, no intuitiva.
import { useCallback } from 'react';
function Parent() {
const [count, setCount] = useState(0);
// Referencia estable — misma función hasta que cambien las deps
const handleClick = useCallback(() => {
setCount(prev => prev + 1);
}, []);
return <Child onClick={handleClick} />;
}
La memorización en useCallback se basa en el almacenamiento en caché del resultado de la llamada a la función. React conserva el closure creado en el primer renderizado y lo devuelve en renderizados posteriores mientras las dependencias permanezcan sin cambios. Dentro del nodo fiber, cada llamada a useCallback crea un nodo en la lista enlazada de hooks, donde se almacenan las dependencias anteriores y el valor memoizado.
La comparación de dependencias se realiza estrictamente mediante Object.is — una comparación superficial sin verificación profunda de objetos o arrays. Si una dependencia es un objeto o array, una nueva referencia en cada renderizado se considerará un cambio. Por lo tanto, el array de dependencias debe contener valores primitivos o referencias estables (por ejemplo, de useRef o useMemo).
Según React Core Team — Optimization Guide (2024), el coste de la memorización incluye tres componentes: asignar el array de dependencias en cada renderizado, recorrer y comparar elementos mediante Object.is, y la posible sobrecarga de recolección de basura al recrear. Para un componente con cientos de envoltorios useCallback, esto puede volverse notable — por lo que la selectividad en el uso del hook es crítica.
| Escenario | Sin useCallback | Con useCallback |
|---|---|---|
| Creación de función | Nueva en cada renderizado | La misma con deps estables |
| Pasar a React.memo | El hijo se rerenderiza | El hijo no se rerenderiza |
| En array de useEffect | El efecto se reinicia | El efecto es estable |
| Sobrecarga | Mínima | Comparación de dependencias + memoria |
Existe un mito generalizado de que useCallback mejora automáticamente el rendimiento. En realidad, de forma aislada (sin React.memo), useCallback incluso ralentiza ligeramente la aplicación debido al coste de la comparación de dependencias. El hook solo aporta un beneficio real en tres escenarios: prevenir rerenderizados de componentes React.memo, estabilizar callbacks en useEffect y pasar callbacks a hooks personalizados que dependen de la igualdad de referencias.
La regla es simple: hasta que no detectes un problema de rendimiento con React DevTools Profiler, no uses useCallback. El equipo de React ha enfatizado repetidamente que la optimización prematura es la raíz de todos los males. Primero escribe código limpio sin memorización, mide, encuentra el cuello de botella en el perfilador y solo entonces añade useCallback donde sea realmente necesario.
// Optimización medible: Child está envuelto en React.memo
const Child = React.memo(({ onClick }) => {
console.log('Child rerenderizado');
return <button onClick={onClick}>Click</button>;
});
function Parent() {
const handleClick = useCallback(() => {
console.log('clicado');
}, []);
return <Child onClick={handleClick} />;
}
Según Dan Abramov — Before You memo() (2024), más del 90% de los casos de uso de useCallback en proyectos open-source son redundantes. Los desarrolladores envuelven cada función "por si acaso" sin medir el efecto. La alternativa: si el componente hijo es pesado y su rerenderizado es costoso — React.memo + useCallback está justificado. Si el componente hijo es ligero — rerenderizar es más barato que comparar dependencias.
El primer escenario es React.memo. Si un componente hijo está envuelto en React.memo y recibe un callback como prop, sin useCallback el componente hijo se rerenderizará en cada renderizado del padre, incluso si sus propios datos no han cambiado. useCallback estabiliza la referencia, permitiendo que React.memo omita correctamente el rerenderizado.
El segundo escenario es useEffect con un callback en dependencias. Si una función se pasa al array de dependencias de useEffect, cada nueva referencia reiniciará el efecto. useCallback asegura que la referencia sea estable, y el efecto solo se ejecuta cuando los datos reales cambian, no en cada renderizado. Esto es especialmente importante para suscripciones y peticiones.
// useCallback para dependencia estable de useEffect
const fetchData = useCallback(async (id) => {
const res = await fetch(`/api/${id}`);
setData(res.data);
}, []); // referencia estable, nunca se recrea
useEffect(() => {
fetchData(props.id);
}, [props.id, fetchData]); // el efecto se ejecuta solo cuando props.id cambia
La diferencia principal entre useCallback y useMemo es lo que cada uno memoiza. useCallback memoiza una función: useCallback(fn, deps) devuelve fn (la misma o la versión anterior). useMemo memoiza el resultado de llamar a una función: useMemo(() => computeExpensive(a, b), [a, b]) devuelve el valor calculado, no una función.
Técnicamente, useCallback es azúcar sintáctico sobre useMemo: useCallback(fn, deps) es equivalente a useMemo(() => fn, deps). Esta sintaxis existe solo para legibilidad — para que el desarrollador vea claramente que se está memoizando una función, no un valor. No hay diferencia de rendimiento entre useCallback y useMemo con una función — generan código idéntico.
| Hook | Memoiza | Sintaxis | Uso |
|---|---|---|---|
| useCallback | Función (referencia) | useCallback(fn, deps) | Callbacks para componentes hijos |
| useMemo | Resultado de cálculo | useMemo(() => value, deps) | Cálculos costosos, memoización de objetos |
// Estos son equivalentes:
const handleClick = useCallback(() => doSomething(a, b), [a, b]);
const handleClick = useMemo(() => () => doSomething(a, b), [a, b]);
El error más común es envolver sin sentido todas las funciones en useCallback sin React.memo en los componentes hijos. Si un componente hijo no está envuelto en React.memo, se rerenderizará igualmente en cada renderizado del padre, independientemente de si la referencia al callback cambia o no. useCallback sin React.memo es coste sin beneficio.
// ❌ Inútil: sin React.memo en el hijo
const handle = useCallback(() => doStuff(), []);
<Child onClick={handle} />; // Child aún se rerenderiza sin React.memo
// ❌ Stale closure: falta dependencia
const handle = useCallback(() => {
console.log(count); // count siempre es 0 — ¡stale closure!
}, []);
// ✅ Correcto: incluir dependencias
const handle = useCallback(() => {
console.log(count);
}, [count]);
El problema de stale closure en useCallback se soluciona incluyendo todas las variables utilizadas en el array de dependencias. eslint-plugin-react-hooks con exhaustive-deps verifica automáticamente que todas las variables del cuerpo del callback estén presentes en el array. Si el callback usa setState, que no cambia entre renderizados, se puede incluir de forma segura en deps — React garantiza la estabilidad de setState.
Preguntas frecuentes
No. useCallback solo tiene sentido en tres casos: el componente hijo está envuelto en React.memo, la función se usa en el array de dependencias de useEffect, o la función se pasa a un hook personalizado que depende de la igualdad de referencia. En el resto de casos, useCallback añade sobrecarga sin beneficio. El equipo de React recomienda escribir primero sin optimizaciones y añadirlas según los resultados del perfilado.
Para componentes simples — una nueva función en cada renderizado es ligeramente más rápida, ya que useCallback gasta recursos en comparar dependencias y asignar el array. Para componentes con árboles profundos de React.memo, useCallback gana al prevenir el rerenderizado de miles de elementos hijos. Mide y compara en lugar de adivinar — usa React DevTools Profiler para una evaluación objetiva.
Sí, useCallback funciona con funciones async exactamente igual que con las síncronas. El hook memoiza la función en sí misma, y el resultado (una Promise) se devuelve cada vez que se llama. Una función async dentro de useCallback es un patrón común para callbacks estables de carga de datos usados en useEffect: const fetchData = useCallback(async (id) => {...}, []).
Usa React DevTools Profiler — muestra qué componentes se rerenderizan y por qué. Para verificación programática, añade console.log o usa useWhyDidYouUpdate — una librería que registra la causa del rerenderizado. Las causas principales: cambió una prop (incluyendo la referencia del callback), cambió el estado o el contexto. Si useCallback no ayuda — verifica que todas las dependencias estén correctamente especificadas.
Incluso sin React.memo, useCallback puede ser útil en combinación con useMemo para valores de contexto. Si pasas un objeto con funciones a Context.Provider, envuelve la creación del objeto en useMemo, y cada función en useCallback. Esto evita el rerenderizado de todos los consumidores del contexto cuando cambia una de las funciones. Pero para pasar callbacks directamente en props sin React.memo, no hay beneficio de useCallback.
Resumen
Desarrollaremos una aplicación móvil llave en mano
IT Sectr crea aplicaciones para iOS y Android para startups y empresas desde 2017. Le asesoraremos y le propondremos la mejor solución.
Lea también