Mantiene la misma referencia de función entre renders, pero siempre ejecuta la versión más reciente. Perfecto para optimizar componentes y listeners.
Devuelve una función que nunca cambia de referencia entre renders, pero siempre ejecuta la versión más reciente de tu callback.
Problema que resuelve:
useCallbackRef: La función cambia en cada render → causa re-renders innecesariosuseCallbackRef: La función mantiene la misma referencia → evita re-rendersimport { useCallbackRef } from "@blifedesarrollo/hooks";function SearchInput({ onSearch }) {
// onSearch puede cambiar en cada render del padre
const stableOnSearch = useCallbackRef(onSearch);
useEffect(() => {
const handler = (e) => stableOnSearch(e.target.value);
const input = document.getElementById("search");
input.addEventListener("input", handler);
return () => input.removeEventListener("input", handler);
// ✅ stableOnSearch nunca cambia, no re-suscribe el evento
}, [stableOnSearch]);
return <input id="search" placeholder="Buscar..." />;
}¿Por qué es útil?
onSearch puede cambiar en cada render del componente padrestableOnSearch mantiene la misma referencia → el useEffect no se re-ejecutaonSearchimport { useState, memo } from "react";
import { useCallbackRef } from "@blifedesarrollo/hooks";
// Componente hijo optimizado con memo
const TaskItem = memo(({ task, onToggle, onDelete }) => {
return (
<div>
<input type="checkbox" checked={task.completed} onChange={() => onToggle(task.id)} />
<span>{task.title}</span>
<button onClick={() => onDelete(task.id)}>Eliminar</button>
</div>
);
});
function TodoApp() {
const [tasks, setTasks] = useState([
{ id: 1, title: "Comprar leche", completed: false },
{ id: 2, title: "Revisar emails", completed: true },
]);
// ✅ Referencia estable - TaskItem no se re-renderiza innecesariamente
const handleToggleTask = useCallbackRef((taskId) => {
setTasks((prevTasks) =>
prevTasks.map((task) =>
task.id === taskId ? { ...task, completed: !task.completed } : task,
),
);
});
const handleDeleteTask = useCallbackRef((taskId) => {
setTasks((prevTasks) => prevTasks.filter((t) => t.id !== taskId));
});
return (
<div>
{tasks.map((task) => (
<TaskItem
key={task.id}
task={task}
onToggle={handleToggleTask} // ← Misma referencia siempre
onDelete={handleDeleteTask} // ← Misma referencia siempre
/>
))}
</div>
);
}Ventaja: TaskItem con memo no se re-renderiza cuando cambia el estado, porque las funciones mantienen la misma referencia.
function Counter() {
const [count, setCount] = useState(0);
// ✅ No necesitas incluir 'count' en dependencias
const handleClick = useCallbackRef(() => {
setCount(count + 1);
// Siempre accede al valor más reciente de 'count'
console.log(`Contador: ${count + 1}`);
});
useEffect(() => {
// handleClick nunca cambia, este efecto solo se ejecuta una vez
window.addEventListener("click", handleClick);
return () => window.removeEventListener("click", handleClick);
}, [handleClick]);
return <div>Count: {count}</div>;
}| Característica | useCallback | useCallbackRef |
|---|---|---|
| Dependencias | Requiere array de deps | No requiere dependencias |
| Referencia estable | Solo si deps no cambian | Siempre estable |
| Versión ejecutada | Versión con deps actuales | Siempre la más reciente |
| Caso de uso | Optimización general | Listeners, memo, refs |
| Parámetro | Tipo | Descripción |
|---|---|---|
callback | T | undefined | Función que quieres mantener referenciada |
| Valor | Tipo | Descripción |
|---|---|---|
stableCallback | T | Función con la misma firma, pero referencia estable entre renders |
memo → Evita re-renders innecesarios de hijos