Isolating components in React: The trick to performance

Isolating components in React: The trick to performance

Regarding performance in React, it's crucial to understand re-renders and their influence. How are they triggered, what happens when a component re-renders and why do we need them? This blog aims to help you understand how isolating components can prevent performance issues.

So let's get started. Imagine working on a large, complicated, and performance-sensitive app. Lots of calls, hooks, and states and you are tasked to add a simple button for a popup on the top of this app.

 const App = ( ) => {

  // lots of things are happening
  return (
    <div className="layout">

      {/* add popup */}

      <VeryComplicatedComponent />
      <TheSlowComponent />
      <BunchofComponent />
    </div>
  );
}
        

Easy right? , seems trivial; every react dev starts here. Just add a state, a trigger button, and the popup itself, that's it.

 const App = ( ) => {

  const [showPopup, setShowPopup] = useState(false)
  return (
    <div className="layout">
      <button onClick={() => { setShowPopup(true) }}>
        show
      </button>

      {
        showPopup ?
          <Popup onClose={() => setShowPopup(false)} />
          : null
      }
      <VeryComplicatedComponent />
      <TheSlowComponent />
      <BunchofComponent />
    </div >
  );
}
        

You run the code, try it out, and wait... why did it take almost a second to show a simple popup???

You run to your senior, GPT or if brave enough you ask on Stackoverflow and they might answer "Well, you are re-rendering the whole app, just wrap everything in React.memo and use useCallback " which is technically true but completely unnecessary in this situation and will do more harm than good. Let's do it the efficient way.

So let's review what's happening here, let’s start from the start: the life of our component. Every re-render in the React starts with state. Each time we use hooks or any external state-management library like Redux we add interactivity to our component. And so, a component will have a piece of data that's preserved throughout its lifecycle. If something changes, we update the data.

Re-rendering is one of the most important things to understand in React. Unless the data is changed, the app will be completely static. If we take our initial example, when the button is clicked we trigger the setShowPopup setter function, and we update the state with a new value as a result the App component that holds that state,re-renders itself, and the new data needs to be delivered to components that depend on it.

React does it automatically for us, grabs all the components, re-renders them, and then re-renders the components nested inside them until the end of the chain of components.

if you imagine a typical React app as a tree, everything down from where the state update was initiated will be re-rendered. In the case of our example, the whole other components, that are not dependent on that data will also be re-rendered. The most important thing to remember here is React never goes up in the render tree, so if a state originated somewhere in the middle of the component tree, only the component down the tree will be re-rendered.

Now, it's time to utilize the things we just went through and solve the initial task, as you can see in the example, the states are relatively isolated; we only use it in the trigger element: Button, and in the Popup itself. The rest of the code is independent. It is what we call an unnecessary re-render.

Wrapping them in React.memo will prevent it but React.memo has many caveats and complexities around it. The better way is to isolate the component, i.e. extract components that depend on that state and the state into a smaller component.


const ButtonWithPopup = ( ) => {

  const [showPopup, setShowPopup] = useState(false)
  return (
    <>
      <button onClick={() => { setShowPopup(true) }}>
        show
      </button >
      {showPopup ?
        <Popup onClose={() => setShowPopup(false)} />
        : null
      }
    </>
  )
};
        

and then, render the new component in the original app.

const App = ( ) => {
  // lots of things are happening 

  return (
    <div className="layout">
      <ButtonWithPopup />
      <VeryComplicatedComponent />
      <TheSlowComponent />
      <BunchofComponent />
    </div>
  );
}
        


Now, the state updates when the button is clicked and some components re-render with it but only the ones inside the ButtonWithPopup component. The rest of the app is safe now.

Essentially, we just created a new sub-branch inside our render tree and moved our state down to it, isolating the popup and it instantly appears. We just fixed a performance issue with a simple composition technique!

Nikolay Petrov

Technical Solutions Wizard | CTO | just ask, happy to help if I can.

10mo

there is only one sure way to improve React code, stop using React.

Sunny Tyagi

Node.js | Angular | Mongodb | Express | Javascript | React.js

10mo

Useful tips

Muhammed Shahil

Youth Ambassador @ ViralFission | Cybersecurity, Event Management

10mo

Insightful!

AKHIL SURYA A

Software Engineer | React.js

10mo

Great advice! 👍

Mahatva garg

PSE@Razorpay | Enhancing Code | problem solver

10mo

Interesting!

To view or add a comment, sign in

More articles by Sanskar Tyagi

Insights from the community

Others also viewed

Explore topics