Understanding React Re-Rendering: How It Affects Performance and Better Solutions

KrishKrish
4 min read

When working with React, especially in complex applications with multiple components, managing re-renders becomes essential. Picture this: you've built a feature-rich component with lots of nested components and heavy calculations. Then, you’re asked to make a small change—say, to control a modal with a simple true or false state. It sounds straightforward, right? Just add a state for the modal, update it to show or hide it as needed, and you’re done.

But here’s the catch: when you change that state, it could cause your entire component to re-render, potentially delaying the modal from appearing instantly due to the time it takes to re-render everything else.

This can lead to a slower user experience, which is where React’s performance optimization techniques come in. Many developers turn to memoization techniques like React.memo, useCallback, or useMemo to limit unnecessary re-renders. But there’s a hidden downside here too—these tools work by watching props and dependencies, which means even small changes in props can accidentally trigger re-renders. Let’s dive into why this happens, and how to get better control over re-rendering without relying on memoization alone.

What Causes Re-Renders in React?

Re-rendering in React is primarily triggered by changes in state or props. When you update a component’s state, React assumes the component needs to re-render, ensuring the UI reflects the latest data. This process is essential, but it can lead to a performance problem if re-renders happen too frequently or across components that don’t need to change.

For example, let’s say you have a parent component controlling a modal, like this:

In this example, each time isOpen changes, the entire ParentComponent re-renders, including HeavyComponent. This re-rendering delay can cause a lag before the modal opens, which may not be ideal.

The Common Solution: Memoization

Memoization is a popular solution to avoid these unnecessary re-renders. You might use React.memo to wrap components, ensuring they only re-render when their props actually change. Or you could use useCallback and useMemo to memoize functions and expensive calculations within components.

  1. React.memo: Wraps a component to prevent it from re-rendering if its props haven’t changed.

  2. useCallback: Memoizes a function so that it keeps the same reference between renders unless its dependencies change.

  3. useMemo: Memoizes a calculated value to avoid recalculating it unnecessarily.

While these can be effective, memoization relies on dependencies—usually props. If any prop changes (even an object that’s technically the same but with a new reference), it can trigger a re-render of the memoized component. So while memoization is helpful, it may not completely solve the problem if we’re passing props around that might change often.

The Better Solution: Move State Down

One powerful technique to avoid unnecessary re-renders is moving state down to the component that actually needs it. Instead of managing state at the parent level, consider keeping it local to the specific component that needs it.

In the example above, instead of managing isOpen in the parent component, we could move it into the Modal component. This way, when isOpen changes, it won’t cause the entire parent component to re-render.

Now, ParentComponent doesn’t re-render whenever isOpen changes, as it’s managed entirely within ModalToggle. This approach reduces unnecessary re-renders, keeping the heavy parent component unaffected by modal state changes.

Props Don’t Always Cause Re-Renders

There’s a common misconception that changing props automatically causes re-renders. In reality, React components re-render when state changes or when they receive a new prop reference. If you pass a primitive value like a string or number, React doesn’t trigger a re-render unless that value changes. However, objects and arrays are different because they’re compared by reference, not value. So, even if an array’s content hasn’t changed, passing a new array reference will still trigger a re-render.

For example :

In this example, Child will only re-render if data changes. If you keep the data object the same (with the same reference), Child won’t re-render, helping avoid unnecessary updates.

Practical Tips for Managing Re-Renders

  1. Move State Down: Keep state close to where it’s used. This reduces the number of components impacted by a state change and keeps re-renders isolated.

  2. Use Local State Whenever Possible: Avoid lifting state unnecessarily to parent components, as this can cause a cascade of re-renders throughout the component tree.

  3. Optimize with Memoization Judiciously: While React.memo, useCallback, and useMemo can prevent re-renders, they should be used selectively and tested. Adding unnecessary memoization adds complexity and can lead to stale values if dependencies are overlooked.

  4. Beware of New Object/Array References as Props: React shallowly compares props, so avoid creating new object/array references unless absolutely necessary. Use useMemo to retain stable references if needed.

Wrapping Up

Understanding and controlling React’s re-rendering behavior can significantly improve application performance, especially in complex components with heavy operations. By moving state down to where it’s actually used and using memoization selectively, you can ensure that your components only re-render when absolutely necessary. Remember, React’s rendering model is highly efficient, but unnecessary re-renders can still impact user experience especially when working with large, complex applications.

When optimizing for performance, always profile your app to understand where re-renders are happening. Small changes like moving state down and using memoization sparingly can make a big difference in delivering a smooth and responsive user experience.

1
Subscribe to my newsletter

Read articles from Krish directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Krish
Krish