JAVASCRIPT
Preventing Unnecessary Rerenders with useCallback
Enhance React component performance by memoizing callback functions using useCallback, ensuring stable references for child components to prevent unnecessary re-renders.
import React, { useState, useCallback, memo } from 'react';
// A child component that only re-renders if its props change
const Button = memo(({ onClick, children }) => {
console.log(`Rendering Button: ${children}`);
return (
<button
onClick={onClick}
className="px-4 py-2 bg-green-500 text-white font-semibold rounded-md shadow-sm hover:bg-green-600 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-green-500 m-2"
>
{children}
</button>
);
});
function ParentComponentWithCallbacks() {
const [count, setCount] = useState(0);
const [text, setText] = useState('');
// This function reference will change on every render of ParentComponentWithCallbacks
const handleIncrementNonMemoized = () => {
setCount(prevCount => prevCount + 1);
};
// This function reference will only change if `count` changes
const handleIncrementMemoized = useCallback(() => {
setCount(prevCount => prevCount + 1);
}, []); // Empty dependency array means this function is created once and never changes
// This function reference will only change if `text` changes
const handleLogText = useCallback(() => {
console.log('Current Text:', text);
}, [text]); // Depends on `text`, so it recreates if `text` changes
return (
<div className="p-4 border rounded shadow-md">
<h2 className="text-lg font-semibold mb-2">useCallback Example</h2>
<p className="mb-3">Count: <span className="font-bold">{count}</span></p>
<div className="mb-3">
<label htmlFor="textInput" className="block text-sm font-medium text-gray-700">Input Text:</label>
<input
id="textInput"
type="text"
value={text}
onChange={(e) => setText(e.target.value)}
className="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50"
/>
</div>
<p className="mt-4 mb-2 font-medium">Buttons demonstrate useCallback impact on child renders:</p>
<Button onClick={handleIncrementNonMemoized}>Increment (Non-Memoized)</Button>
<Button onClick={handleIncrementMemoized}>Increment (Memoized with [])</Button>
<Button onClick={handleLogText}>Log Text (Memoized with [text])</Button>
</div>
);
}
export default ParentComponentWithCallbacks;
How it works: This snippet showcases `useCallback` to prevent unnecessary re-renders of child components. In React, a new function is created on every render of a parent component. If this function is passed as a prop to a child component (especially one wrapped in `React.memo`), the child will re-render even if its actual logic hasn't changed, because the prop's *reference* has changed. `useCallback` memoizes the function, ensuring its reference remains stable across renders as long as its dependencies haven't changed. The `handleIncrementMemoized` function, with an empty dependency array `[]`, is created only once. `handleLogText` recreates only when `text` changes. Notice how the 'Increment (Non-Memoized)' button causes all `Button` components to re-render, while the `useCallback` buttons only re-render if their relevant dependencies (or props from other sources) actually change.