Stop Re rendering Child Component with React memo()

Memoize component with memo react for load app faster


React executes the component whenever the state, context, and props value change, but do you know every time the parent component updates, its nested components will also re-evaluate. 

Which is normal behavior of React. However, if the child component contains heavy calculation or large data management then it would negatively affect the performance of the parent component even if update won't have anything to do with child components. 

In this case, if you don't want to re-render a child component, you can utilize React's built-in method memo function.

In this article, I will discuss component optimization and how to prevent a child component from re-rendering when its parent component is updated.

React memo()

Memo function is a built-in performance optimization technique that is used to memoize the result of the component. It returns the cached result when the same output occurs again and speeds up the execution.

It will keep track of the previous prop's value and compare it with the current value. If it detects changes, it will only execute the child component again and re-evaluate it; if not, it will not execute it at all.

Let's see why the memo function is essential before I go over how to utilize it.

I have a simple application, a list of tabs and when I switch tabs it renders the corresponding content. 

import ActiveTasks from "./components/ActiveTasks"
import AllTasks from "./components/AllTasks"
import CompletedTasks from "./components/CompletedTasks"
import TabBtn from "./components/TabBtn"

import { useState } from "react"

function App() {
  const [activeTab, setActiveTab] = useState('all')
  const [newTask, setNewTask] = useState('')

  const handleClick = (tabName) => {
    setActiveTab(tabName)
  }
  const handleNewTaskChange = (e) => {
    setNewTask(e.target.value)
  }
  return (
    <section className="tasks">
      <label>New Task</label>
      <input value={newTask} type="text" onChange={handleNewTaskChange} />
      <menu>
        <TabBtn onClick={() => handleClick('all')} activeId={activeTab === 'all'}>All</TabBtn>
        <TabBtn onClick={() => handleClick('completed')} activeId={activeTab === 'completed'}>Completed</TabBtn>
        <TabBtn onClick={() => handleClick('active')} activeId={activeTab === 'active'}>Active</TabBtn>
      </menu>
      <div>
        {activeTab === 'all' && <AllTasks title="User Tasks" />}
        {activeTab === 'completed' && <CompletedTasks title="Completed Tasks" />}
        {activeTab === 'active' && <ActiveTasks title="Active Tasks" />}
      </div>
    </section>
  )
}

export default App; 

In the following code, we used state to change the UI, and I have three nested components within the App component, where I'm passing props title to those child components.

const AllTasks = ({title}) => {
  console.log('<AllTasks />')
  return (
    <div className="title">{title}</div>
  )
}

const CompletedTasks = ({title}) => {
  console.log('<CompletedTasks />')
  return (
    <div className="title">{title}</div>
  )
}

const ActiveTasks = ({title}) => {
  console.log('<ActiveTasks />')
  return (
    <div className="title">{title}</div>
  )
}

Now when I click the button, React will re-execute that component and Ui will update information based on the selected tab. In every child component I've passed console so every time it re-renders, it'll log the message to console.

Inside the App component, I also have an input field where due to state changes, every letter will force the App component to re-render, however every time the parent component is updated, the child components will also be re-rendered, even if they are not required.

In small applications it's okay because React will execute in an instant but what if one of child component contains expensive calculations. 

import { utils } from '../utils'

const CompletedTasks = memo(({title}) => {
  const expensiveCalculaion = utils()

  console.log('<CompletedTasks />')
  return (
    <div className="title">{title}</div>
  )
})

export default CompletedTasks 

Here utils() function will loop over billions of numbers every time child component re-render, therefore every letter of input field will take a moment to render even though the input field is unrelated to the child component

Use React memo() in component

To memoize a component wrap CompletedTasks component or any other child arrow functional component with memo. Also you need to import memo method from react library.

Now our CompletedTasks component will look like this;

import { utils } from '../utils'

import { memo } from 'react'

const CompletedTasks = memo(({title}) => {
  const expensiveCalculaion = utils()

  console.log('<CompletedTasks />')
  return (
    <div className="title">{title}</div>
  )
})

Memo function only applies to its parent component and child component will still run if its own state or context change. 

Now let's change the props title value to `All completed tasks`.

<CompletedTasks title="All Completed Tasks" />

React will notice the changes and re-render the child component. The UI will be updated with new props, and the console will be run again.

Avoid using memo() function unnecessarily

Memo function also has some downside and it's recommended to not use it everywhere. Whenever parent executes, React performs a comparison for child component.

To avoid that circumstance we can use some technique.

  • You can wrap higher root component with memo function so it'll take care of all nested child components.
  • Avoid using memo function if props are going to change on every execution.
  • If React application is small and doesn't contain expensive calculation then don't perform memoization.
  • Profile parent component and adjust some code.to minimize props change.

Conclusion

React memo() function plays a crucial role in application optimization. it returns same cached output as long as props haven't changed and prevents unnecessary re-rendering.

0 Comments:

Post a Comment