DEV Community

Favour Okpara
Favour Okpara

Posted on

Understanding React's Rendering Behavior: What Actually Triggers a Re-render?

TL;DR

React components re-render for exactly four reasons: state changes (useState/useReducer), parent component re-renders, context value changes, and custom hook state changes. Props changing doesn't directly cause re-renders—the parent re-rendering does. Mutating regular variables or refs won't trigger re-renders. React is fast by default, so only optimize when you have a proven performance issue.


If you've ever wondered why your React component re-rendered when you didn't expect it to, or worse, why it didn't re-render when you needed it to, you're not alone. React's rendering behavior can feel magical at first, but understanding what's happening under the hood will make you a more confident developer.

Let's break down exactly what triggers re-renders in React, clear up common misconceptions, and build a mental model you can rely on.

What is a "Render" Anyway?

Before we dive into triggers, let's clarify what we mean by "render." In React, rendering is the process of calling your component function (or the render() method in class components) to determine what should be displayed.

function MyComponent() {
  console.log('Component is rendering!');
  return <div>Hello World</div>;
}
Enter fullscreen mode Exit fullscreen mode

Every time you see that console.log fire, React is rendering your component. But rendering doesn't automatically mean the DOM updates. React first calculates what should change, then only updates the actual DOM if necessary. This is a crucial distinction.

The Four Fundamental Re-render Triggers

There are exactly four things that cause a React component to re-render:

1. State Changes

When you call a state setter function (from useState or useReducer), React schedules a re-render of that component.

function Counter() {
  const [count, setCount] = useState(0);

  const increment = () => {
    setCount(count + 1); // Triggers a re-render
  };

  return <button onClick={increment}>{count}</button>;
}
Enter fullscreen mode Exit fullscreen mode

Note: React uses Object.is() comparison to check if the state actually changed. If you set state to the same value it already has, React will bail out of the re-render.

const [count, setCount] = useState(5);
setCount(5); // No re-render! Value didn't change
Enter fullscreen mode Exit fullscreen mode

2. Parent Component Re-renders

This is the source of many "Why did this re-render?" moments. When a parent component re-renders, all of its children re-render by default, regardless of whether their props changed.

function Parent() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <button onClick={() => setCount(count + 1)}>Click me</button>
      <Child /> {/* Re-renders every time Parent does */}
    </div>
  );
}

function Child() {
  console.log('Child rendered!');
  return <div>I'm a child</div>;
}
Enter fullscreen mode Exit fullscreen mode

Even though Child receives no props and has no state, it re-renders whenever Parent does. This is React's default behavior. React assumes that a parent re-rendering might affect its children.

3. Context Changes

When a context value changes, every component that consumes that context (via useContext or Context.Consumer) will re-render.

const ThemeContext = createContext();

function App() {
  const [theme, setTheme] = useState('light');

  return (
    <ThemeContext.Provider value={theme}>
      <Toolbar />
    </ThemeContext.Provider>
  );
}

function Toolbar() {
  // This component re-renders when theme changes
  const theme = useContext(ThemeContext);
  return <div className={theme}>Toolbar</div>;
}
Enter fullscreen mode Exit fullscreen mode

Note: Even if a component sits between the Provider and the consumer, the consumer will still re-render when the context changes, even if the middle component doesn't.

4. Hook Changes

When a custom hook's internal state or subscribed values change, components using that hook will re-render.

function useWindowWidth() {
  const [width, setWidth] = useState(window.innerWidth);

  useEffect(() => {
    const handleResize = () => setWidth(window.innerWidth);
    window.addEventListener('resize', handleResize);
    return () => window.removeEventListener('resize', handleResize);
  }, []);

  return width;
}

function MyComponent() {
  const width = useWindowWidth(); // Re-renders when window is resized
  return <div>Width: {width}</div>;
}
Enter fullscreen mode Exit fullscreen mode

What DOESN'T Trigger Re-renders

Just as important as knowing what triggers re-renders is knowing what doesn't:

Props Changes Alone Don't Trigger Re-renders

This surprises people, but it's true. Props changing doesn't directly cause a re-render, the parent re-rendering does. Props changing is just a consequence of the parent re-rendering and passing new values down.

function Parent() {
  const [count, setCount] = useState(0);
  return <Child value={count} />; // Parent re-renders, Child re-renders
}
Enter fullscreen mode Exit fullscreen mode

The Child doesn't re-render "because props changed." It re-renders because Parent re-rendered, which happens to result in new props being passed.

Regular Variable Changes

Mutating regular variables or object properties doesn't trigger re-renders. Only state changes do.

function Broken() {
  let count = 0; // NOT state

  const increment = () => {
    count++; // This won't trigger a re-render!
    console.log(count); // Will log incremented value
  };

  return <button onClick={increment}>{count}</button>; // UI won't update
}
Enter fullscreen mode Exit fullscreen mode

Ref Changes

Updating a ref (via useRef) doesn't trigger re-renders. That's actually the point of refs, they're for values that need to persist across renders without causing re-renders.

function Component() {
  const renderCount = useRef(0);

  renderCount.current++; // No re-render triggered

  return <div>Rendered {renderCount.current} times</div>;
}
Enter fullscreen mode Exit fullscreen mode

Common Misconceptions

"React re-renders when props change"

As we discussed, this is backwards. React re-renders the component, which results in potentially different props being passed to children.

"Re-rendering is bad and should be avoided"

Re-rendering is how React works! It's not inherently bad. React is very fast at rendering, and most components render in microseconds. Only optimize re-renders if you have a proven performance problem.

"Setting state to the same value always causes a re-render"

React will bail out of re-renders if you set state to the same value (using Object.is() comparison). However, this bailout happens after React has started the re-render process, so the component function will be called once more, but children won't re-render.

Building Your Mental Model

Here's a simple mental model to internalize:

  1. State changes are the root cause of all re-renders (including context and hook state)
  2. Re-renders cascade down the component tree by default
  3. React compares before committing to the DOM (rendering ≠ DOM updates)
  4. Optimization tools exist when needed (React.memo, useMemo, useCallback)

When Should You Care About Re-renders?

Most of the time, you shouldn't. React is fast, and premature optimization wastes time. You should investigate re-renders when:

  • You notice visible lag or stuttering in your UI
  • You have components doing expensive calculations on every render
  • You're rendering large lists (hundreds or thousands of items)
  • Your profiler shows components taking significant time to render

Practical Example: Debugging Unexpected Re-renders

Let's say you have a component re-rendering too much. Here's how to debug it:

function MyComponent({ data }) {
  // Add this to see when and why component renders
  useEffect(() => {
    console.log('MyComponent rendered');
  });

  return <div>{data.value}</div>;
}
Enter fullscreen mode Exit fullscreen mode

Then ask yourself:

  1. Is the parent re-rendering? (Check parent's render logs)
  2. Is state changing in this component? (Log state values)
  3. Is context changing? (Log context values)
  4. Are we getting new prop objects on every render? (Log data identity)

Conclusion

React's rendering behavior follows consistent, predictable rules. State changes trigger re-renders, which cascade to children by default. Understanding this helps you reason about your app's behavior and know when (and when not) to optimize.

The key insight: React re-renders are not triggered by props changes or variable assignments, they're triggered by state changes and then propagate down the component tree. Everything else follows from this fundamental truth.

Now when you see an unexpected re-render, you'll know exactly where to look: trace back to find which state changed, and you'll find your answer.


Have questions about React rendering? Found this helpful? Want me to dive deeper into when to use useMemo, useCallback, and React.memo for optimization? Let me know in the comments below!

Top comments (0)