React LogoPerformance Optimization (Memoization, useMemo, useCallback)

React applications, especially complex ones, can suffer from performance issues due to unnecessary re-renders. When a component's state or props change, React re-renders that component and all its children by default. This can lead to wasted computational cycles if the re-render doesn't result in a visual change or involves expensive calculations. Performance optimization techniques like memoization help prevent these unnecessary computations and re-renders.

What is Memoization?
Memoization is an optimization technique used primarily to speed up computer programs by storing the results of expensive function calls and returning the cached result when the same inputs occur again. In React, this concept is applied to components, values, and functions to avoid re-execution or re-rendering when their dependencies haven't changed.

`useMemo` Hook
`useMemo` is a React Hook that memoizes a *value*. It allows you to cache the result of an expensive calculation and only recompute it when one of its dependencies changes. This prevents the computation from running on every render.

Syntax: `const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);`

* The first argument is a function that returns the value you want to memoize.
* The second argument is an array of dependencies. The memoized value will only be recomputed if any of these dependencies change.

When to use:
* Performing expensive calculations (e.g., data filtering, sorting, transformations) that don't need to run on every render.
* Ensuring referential equality for object or array props passed to `React.memo`ized child components to prevent unnecessary re-renders of the child.

`useCallback` Hook
`useCallback` is a React Hook that memoizes a *function*. It returns a memoized version of the callback function that only changes if one of its dependencies has changed. This is crucial for optimizing child components that rely on referential equality to avoid re-rendering.

Syntax: `const memoizedCallback = useCallback(() => doSomething(a), [a]);`

* The first argument is the function you want to memoize.
* The second argument is an array of dependencies. The memoized function will only be re-created if any of these dependencies change.

When to use:
* Passing callbacks to `React.memo`ized child components. Without `useCallback`, a new function instance would be created on every parent render, causing the child to re-render even if its actual logic hasn't changed, because the prop's reference has changed.
* When a function is a dependency in other Hooks like `useEffect` or `useMemo` to prevent those Hooks from re-running unnecessarily.

`React.memo` Higher-Order Component (HOC)
`React.memo` is a higher-order component (HOC) that memoizes a *component*. It works similarly to `shouldComponentUpdate` for class components. It will prevent a functional component from re-rendering if its props have not shallowly changed.

Syntax: `export default React.memo(MyFunctionalComponent);`

How it works with `useMemo` and `useCallback`:
`React.memo` performs a shallow comparison of props. If props are primitive values (strings, numbers, booleans), shallow comparison works perfectly. However, if props are objects, arrays, or functions, they are considered different on every render (due to new memory references) even if their contents are identical.

* `useMemo` helps maintain referential equality for object/array props passed to `React.memo`ized components.
* `useCallback` helps maintain referential equality for function props passed to `React.memo`ized components.

By combining `React.memo` with `useMemo` and `useCallback`, you can effectively prevent child components from re-rendering when their props haven't truly changed, leading to significant performance gains.

When to use these optimizations (and when not to)
* Use when: You observe actual performance bottlenecks (e.g., slow interactions, janky animations) caused by unnecessary re-renders or expensive computations. Use profiling tools (React DevTools) to identify these bottlenecks.
* Don't overuse: Memoization comes with its own overhead (memory for caching, comparison logic). Applying it everywhere can lead to 'over-optimization,' where the cost of memoization outweighs the benefit, potentially making your code harder to read and debug. Focus on areas identified as performance critical.

Example Code

```jsx
import React, { useState, useMemo, useCallback } from 'react';

// --- Child Component: Optimized with React.memo ---
// This component will only re-render if its props change.
const MemoizedDisplay = React.memo(({ count, onIncrement }) => {
  console.log('MemoizedDisplay component rendered');
  return (
    <div style={{ border: '1px solid lightblue', padding: '10px', margin: '10px' }}>
      <h3>Child Component (MemoizedDisplay)</h3>
      <p>Current Count: {count}</p>
      <button onClick={onIncrement}>Increment Count from Child</button>
      <p>This component re-renders ONLY when its 'count' or 'onIncrement' prop changes reference.</p>
    </div>
  );
});

// --- Helper function for an expensive calculation ---
const calculateFactorial = (num) => {
  console.log(`Calculating factorial for ${num}...`);
  if (num < 0) return -1;
  if (num === 0) return 1;
  let result = 1;
  for (let i = 1; i <= num; i++) {
    // Simulate a very expensive calculation
    for (let j = 0; j < 10000000; j++) {}
    result *= i;
  }
  return result;
};

// --- Parent Component ---
function App() {
  const [count, setCount] = useState(0);
  const [number, setNumber] = useState(5); // For factorial calculation

  // 1. useMemo: Memoize the result of an expensive calculation
  // factorial will only be re-calculated if 'number' changes.
  const factorial = useMemo(() => calculateFactorial(number), [number]);

  // 2. useCallback: Memoize a function to prevent it from being re-created on every render.
  // This is crucial for MemoizedDisplay if onIncrement is passed as a prop.
  // The function reference only changes if 'count' changes.
  const handleIncrement = useCallback(() => {
    setCount((prevCount) => prevCount + 1);
  }, []); // Empty dependency array means this function reference never changes after initial render

  // If we wanted handleIncrement to depend on `count` for some reason:
  // const handleIncrement = useCallback(() => {
  //   setCount(count + 1); // This would be the `count` value from the render when the function was created
  // }, [count]); 
  // However, for simple increments, an empty array or using the functional update form is preferred to avoid stale closures.


  console.log('App component rendered');

  return (
    <div style={{ fontFamily: 'Arial', padding: '20px' }}>
      <h2>Performance Optimization in React</h2>

      <div style={{ border: '1px solid gray', padding: '15px', marginBottom: '20px' }}>
        <h3>Parent Component (App)</h3>
        <p>App Render Count: {count}</p>
        <button onClick={handleIncrement}>Increment Count from Parent</button>

        <p>Number for Factorial: {number}</p>
        <button onClick={() => setNumber((prev) => prev + 1)}>
          Increase Number for Factorial
        </button>

        {/* Display the memoized factorial */}
        <p>
          Factorial of {number} (memoized with <code>useMemo</code>): <strong>{factorial}</strong>
        </p>
        <p>Try changing the 'App Render Count' - the factorial calculation won't re-run. Only 'number' changes it.</p>
      </div>

      {/* 
        Passing memoized 'count' and 'handleIncrement' (from useCallback) to the child. 
        Since 'handleIncrement' reference doesn't change and 'count' is a primitive (always same reference if value is same),
        MemoizedDisplay will not re-render unnecessarily when only 'number' changes in App.
      */}
      <MemoizedDisplay count={count} onIncrement={handleIncrement} />

      <div style={{ marginTop: '20px', padding: '10px', borderTop: '1px dashed #ccc' }}>
        <h4>Observations:</h4>
        <ul>
          <li>Open your browser's console to see render logs.</li>
          <li>Click 'Increment Count from Parent' or 'Increment Count from Child': Both App and MemoizedDisplay re-render because `count` prop changes.</li>
          <li>Click 'Increase Number for Factorial': Only App re-renders. `calculateFactorial` runs only if `number` changes. `MemoizedDisplay` does NOT re-render because its `count` and `onIncrement` props (references) have not changed, thanks to `React.memo` and `useCallback`.</li>
        </ul>
      </div>
    </div>
  );
}

export default App;
```