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.

Need help integrating this into your project?

Our team of expert developers can help you build your custom application from scratch.

Hire DigitalCodeLabs