useRef: The Escape Hatch
The useRef Hook serves two primary purposes:
1. Accessing DOM Elements: This is its most common use case. It provides a way to get a direct reference to a DOM element rendered by your component. This is useful for managing focus, triggering animations, or integrating with third-party DOM libraries.
JavaScript
import React, { useRef, useEffect } from 'react';
function TextInputWithFocus() {
// Create a ref object
const inputRef = useRef(null);
// Use useEffect to focus the input when the component mounts
useEffect(() => {
// The actual DOM node is available on the .current property
inputRef.current.focus();
}, []); // Empty dependency array means this runs only once
return <input ref={inputRef} type="text" />;
}
2. Storing a Mutable Value that Doesn't Cause a Re-render: Unlike useState, updating a ref's .current property does not trigger a re-render. This makes useRef perfect for storing information that needs to persist across renders but doesn't directly affect the visual output, like a timer ID or a previous state value.
JavaScript
const timerIdRef = useRef(null);
const startTimer = () => {
timerIdRef.current = setInterval(() => { ... });
};
const stopTimer = () => {
clearInterval(timerIdRef.current);
};
Performance Hooks: useMemo and useCallback
These hooks are tools for optimization. You should not use them everywhere, only when you have a measurable performance problem.
useMemo: Memoizing Values The useMemo Hook is for memoizing the result of an expensive calculation. It takes a function and a dependency array. React will only re-run the function (and re-calculate the value) if one of the dependencies has changed.
Imagine a function that filters a huge list, which is a slow operation:
JavaScript
import React, { useState, useMemo } from 'react';
function TodoList({ todos, filter }) {
// This expensive calculation runs on every single render
// const visibleTodos = filterTodos(todos, filter);
// With useMemo, filterTodos will only be called again
// if 'todos' or 'filter' changes.
const visibleTodos = useMemo(() => {
console.log("Running expensive filtering...");
return filterTodos(todos, filter);
}, [todos, filter]);
return (
<ul>
{visibleTodos.map(todo => <li key={todo.id}>{todo.text}</li>)}
</ul>
);
}
If the component re-renders for another reason (e.g., a parent's state changes), useMemo will return the previously calculated visibleTodos array without running the expensive filtering logic again.
useCallback: Memoizing Functions The useCallback Hook is very similar, but instead of memoizing a value, it memoizes a function. This is useful when you are passing callbacks to child components that are optimized to only re-render when their props change.
JavaScript
import React, { useState, useCallback } from 'react';
import MyButton from './MyButton'; // Assume MyButton is a memoized component
function ParentComponent() {
const [count, setCount] = useState(0);
// Without useCallback, a new handleClick function is created on every render.
// This would cause MyButton to re-render even if it's memoized.
// const handleClick = () => {
// console.log("Button clicked!");
// };
// With useCallback, the same function instance is returned as long
// as its dependencies (none, in this case) don't change.
const handleClick = useCallback(() => {
console.log("Button clicked!");
}, []); // Empty dependency array
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(c => c + 1)}>Increment</button>
<MyButton onClick={handleClick} />
</div>
);
}
By wrapping handleClick in useCallback, we ensure that the MyButton component receives the exact same function prop on every render, preventing unnecessary re-renders.