React is known for being fast. Today, we will explore "What makes it fast?" and "How can we make it even faster?"

What Triggers a Render?

In React, a component re-renders when,

Reconciliation: How React Knows What to Change

In React, Reconciliation is the process used to determine how efficiently update the DOM when any change occurs in the UI.

Firstly, React calls the render() method of a component to create a new Virtual DOM, and then compares it with the previous Virtual DOM. After calculating the minimum number of changes needed, it updates only the changed parts of the real DOM. This complete process comes under the Reconciliation.

How does it compare to the Elements?

Example: This example shows list rendering.

JavaScript
const users = [{ id: 1, name: "Alice" }, { id: 2, name: "Bob" }];
return (
  <ul>
    {users.map((u) => (
      <li key={u.id}>{u.name}</li>
    ))}
  </ul>
);

If your list is not static, never use an index, as it can break reconciliation. Always use stable keys (such as IDs).

Batching: Grouping Updates Into One Render

The process of combining or grouping multiple state updates into a single re-render is known as Batching. It improves the application's performance by reducing the frequency of DOM updates.

Before React 18, state updates and async calls were not batched; only event handlers were triggered, and updates were batched. However, with React 18 and automatic batching, State updates are now batched in all contexts, including those involving promises and async/await.

JavaScript
function App() {
  const [x, setX] = useState(0);
  const [y, setY] = useState(0);
  function updateBoth() {
    setX((prev) => prev + 1);
    setY((prev) => prev + 1);
  }
  return <button onClick={updateBoth}>Click</button>;
}

The above code triggers only one render, even though two states changed.

Memoization: Preventing Wasted Renders

It is about caching results of computations or render logic so that React doesn't re-do the same work over and over again.

React.memo

The react. Memo is used to prevent a component from re-rendering if its props haven't changed.

JavaScript
const Profile = React.memo(function Profile({ name }) {
  console.log("Rendering Profile...");
  return <p>{name}</p>;
});

In the above code, even if the parent re-renders, Profile won't re-render unless the name prop changes.

useMemo

It caches the result of an expensive calculation (such as sorting, filtering, or heavy computation) to avoid repeating it unnecessarily, as doing so every time can slow things down.

JavaScript
import React, { useMemo, useState } from 'react';

function ItemList({ items }) {
  const [query, setQuery] = useState("");

  const sortedItems = useMemo(() => {
    console.log("Sorting items...");
    return [...items].sort((a, b) => a.value - b.value);
  }, [items]);

  return (
    <>
      <input
        value={query}
        onChange={(e) => setQuery(e.target.value)}
        placeholder="Search"
      />
      <ul>
        {sortedItems
          .filter(item => item.name.includes(query))
          .map(item => <li key={item.id}>{item.name}</li>)}
      </ul>
    </>
  );
}

Without useMemo, Sorting happens on every keystroke in the input. Even if items didn't change, it still sorts again. However, with useMemo, sorting only occurs when items change, resulting in faster renders while typing into the input.

useCallback

It caches the function reference between renders, because in JavaScript, functions are re-created every time a component renders.

Suppose you pass a function as a prop to React.Memoized child, the child still re-renders because the function is technically a new object each time, even if the logic hasn’t changed. useCallback() keeps the function reference the same, unless its dependencies change.

Example: Callback Optimization with Child Component.

JavaScript
const Child = React.memo(({ onClick }) => {
  console.log("Child rendered");
  return <button onClick={onClick}>Click Me</button>;
});
JavaScript
function Parent() {
  const [count, setCount] = useState(0);

  // Without useCallback, new function every render
  // With useCallback, same function reference
  const handleClick = useCallback(() => {
    console.log("Clicked");
  }, []);

  return (
    <>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
      <Child onClick={handleClick} />
    </>
  );
}

Conclusion

React is designed to render efficiently by default. But as your app scales, you need to help React help you.

With these tools, you’ll be able to build fast, fluid, and responsive apps, even as complexity grows.