📋 Tabla de contenidos
-
useState
– El fundamento del estado -
useEffect
– Efectos secundarios y ciclo de vida -
useContext
– Compartir estado sin props -
useReducer
– Lógica de Estado Compleja y predecible -
useRef
– Referencias, Almacenamiento y Escape -
useMemo
yuseCallback
– Optimizando el Rendimiento - Custom Hooks – Creando Lógica Reutilizable
- Errores Comunes y Soluciones
El problema: re-renderizados innecesarios y la igualdad referencial
Antes de entender la solución, es crucial entender el problema. En JavaScript, los objetos y las funciones son iguales por referencia, no por valor.
// A pesar de tener el mismo "valor", son dos objetos diferentes en memoria.
{} === {} // false
// Lo mismo ocurre con las funciones.
(() => {}) === (() => {}) // false
En React, cada vez que un componente se re-renderiza, las funciones y objetos declarados dentro de él se crean de nuevo. Esto significa que sus referencias cambian en cada render.
¿Por qué es un problema? Si pasas una de estas funciones u objetos como prop a un componente hijo optimizado con React.memo
, el hijo se re-renderizará de todas formas, porque desde su perspectiva, la prop (onSomething
) ha cambiado.
Aquí es donde entran useMemo
y useCallback
. Son herramientas para decir a React: "No vuelvas a crear esto a menos que sus dependencias cambien".
El problema: re-renderizados innecesarios y la igualdad referencial
Antes de entender la solución, es crucial entender el problema. En JavaScript, los objetos y las funciones son iguales por referencia, no por valor.
// A pesar de tener el mismo "valor", son dos objetos diferentes en memoria.
{} === {} // false
// Lo mismo ocurre con las funciones.
(() => {}) === (() => {}) // false
En React, cada vez que un componente se re-renderiza, las funciones y objetos declarados dentro de él se crean de nuevo. Esto significa que sus referencias cambian en cada render.
¿Por qué es un problema? Si pasas una de estas funciones u objetos como prop a un componente hijo optimizado con React.memo
, el hijo se re-renderizará de todas formas, porque desde su perspectiva, la prop (onSomething
) ha cambiado.
Aquí es donde entran useMemo
y useCallback
. Son herramientas para decir a React: "No vuelvas a crear esto a menos que sus dependencias cambien".
useMemo
: Memorizando valores calculados
useMemo
"memoriza" (cachea) el resultado de un cálculo costoso. La función que le pasas solo se volverá a ejecutar si una de las dependencias ha cambiado.
Úsalo para:
- Cálculos computacionalmente caros (ej. filtrar, ordenar o transformar grandes listas).
- Evitar que se creen nuevos objetos/arrays en cada render si se pasan como props a componentes optimizados.
Sintaxis:
const valorMemorizado = useMemo(() => calcularValorCostoso(a, b), [a, b]);
useCallback
: Memorizando funciones
useCallback
es muy similar, pero en lugar de memorizar el resultado de una función, memoriza la propia definición de la función.
Úsalo para:
- Evitar re-renderizados en componentes hijos optimizados (
React.memo
) que reciben funciones como props. - Como dependencia estable en otros hooks como
useEffect
.
Sintaxis:
const funcionMemorizada = useCallback(() => {
hacerAlgo(a, b);
}, [a, b]);
Es conceptualmente equivalente a: useMemo(() => () => { hacerAlgo(a, b); }, [a, b])
.
Ejemplo práctico detallado: lista filtrable optimizada
Imagina una lista de usuarios que puedes filtrar. El filtrado puede ser lento si la lista es grande. Además, cada item de la lista tiene un botón para eliminarlo.
Componente UserItem
(Optimizado)
import React from 'react';
// React.memo evita que este componente se re-renderice si sus props no cambian.
const UserItem = React.memo(({ user, onDelete }) => {
console.log(`Renderizando UserItem para: ${user.name}`);
return (
<li>
{user.name}
<button onClick={() => onDelete(user.id)}>Eliminar</button>
</li>
);
});
Componente Principal UserList
import React, { useState, useMemo, useCallback } from 'react';
const initialUsers = [
{ id: 1, name: 'Ana' },
{ id: 2, name: 'Carlos' },
{ id: 3, name: 'Beatriz' },
{ id: 4, name: 'Daniel' },
];
function UserList() {
const [users, setUsers] = useState(initialUsers);
const [filter, setFilter] = useState('');
const [theme, setTheme] = useState('light'); // Estado extra para forzar re-renders
// 1. Optimizando el cálculo del filtrado con `useMemo`
// Esta función solo se re-ejecutará si `users` o `filter` cambian,
// no cuando `theme` cambie.
const filteredUsers = useMemo(() => {
console.log('Filtrando usuarios...');
return users.filter(user =>
user.name.toLowerCase().includes(filter.toLowerCase())
);
}, [users, filter]);
// 2. Optimizando la función de borrado con `useCallback`
// `handleDelete` mantiene la misma referencia mientras `users` no cambie.
// Esto evita que todos los `UserItem` se re-rendericen cuando cambiamos el tema o el filtro.
const handleDelete = useCallback((userId) => {
setUsers(prevUsers => prevUsers.filter(user => user.id !== userId));
}, []); // Dependencia vacía porque usamos la actualización funcional de `setUsers`
return (
<div style={{ background: theme === 'light' ? 'white' : 'black', color: theme === 'light' ? 'black' : 'white' }}>
<button onClick={() => setTheme(t => t === 'light' ? 'dark' : 'light')}>
Cambiar Tema
</button>
<hr />
<input
type="text"
placeholder="Filtrar usuarios..."
value={filter}
onChange={e => setFilter(e.target.value)}
/>
<ul>
{filteredUsers.map(user => (
<UserItem key={user.id} user={user} onDelete={handleDelete} />
))}
</ul>
</div>
);
}
✅ Buenas practicas y patrones comunes
- NO optimices prematuramente:
useMemo
yuseCallback
tienen un coste. Usarlos en todos lados puede hacer tu aplicación más lenta. Aplícalos solo cuando hayas identificado un problema de rendimiento real (puedes usar el Profiler de React DevTools). -
React.memo
es tu mejor amigo:useCallback
por sí solo no hace nada si el componente que recibe la función no está envuelto enReact.memo
(oPureComponent
). - Dependencias, dependencias, dependencias: El array de dependencias es la clave. Asegúrate de incluir todos los valores del scope del componente que se usan dentro de la función memorizada. El linter de React te ayudará con esto.
🚨 Errores comunes y cómo evitarlos
- Error: Usar
useMemo
para memorizar componentes.- Solución: Para memorizar un componente, envuélvelo en
React.memo
.useMemo
es para valores, no para JSX.
- Solución: Para memorizar un componente, envuélvelo en
- Error: Olvidar el array de dependencias.
- Solución: Si omites el array, la función se recalculará en cada render, haciendo la optimización inútil. Si quieres que se calcule solo una vez, usa
[]
.
- Solución: Si omites el array, la función se recalculará en cada render, haciendo la optimización inútil. Si quieres que se calcule solo una vez, usa
-
Error: Pasar una función creada en el cuerpo del componente a las dependencias de
useEffect
.
// MAL ❌ function MiComponente() { const fetchData = () => { /* ... */ }; useEffect(() => { fetchData(); }, [fetchData]); // `fetchData` es nueva en cada render -> bucle infinito }
- Solución: Define la función dentro del
useEffect
si solo se usa ahí, o envuélvela enuseCallback
si se usa en varios sitios.
// BIEN ✅ const fetchData = useCallback(() => { /* ... */ }, []); useEffect(() => { fetchData(); }, [fetchData]);
- Solución: Define la función dentro del
🚀 Retos prácticos
- Cálculo de Factorial: Crea un componente con un input numérico. Calcula y muestra el factorial de ese número. El cálculo debe estar memorizado con
useMemo
. Añade otro estado (como un toggle de tema) para verificar que el factorial no se recalcula innecesariamente. - Lista de Hijos con
React.memo
: Crea un componente padre que renderiza una lista de 100 componentes hijos. Pasa una funciónonPress
al hijo. Comprueba con unconsole.log
cuántos hijos se re-renderizan cuando el padre cambia su estado. Luego, optimiza conuseCallback
yReact.memo
y comprueba la diferencia. - Gráfico SVG Complejo: Simula un componente que renderiza un gráfico SVG (que puede ser costoso). Las coordenadas del gráfico se calculan a partir de un array de datos. Usa
useMemo
para memorizar las coordenadas calculadas y evitar re-calcularlas si otras props del componente cambian.
Top comments (0)