React Performance Optimization

Sagar - Apr 26 '20 - - Dev Community

Hey folks, I hope you' all doing well.

After a long time break, I'm again focusing on writing articles on dev.to. In this article, I want to cover a way to avoid re-renders of react components.

#1 Avoid passing unnecessary props to children components

Component with limited props is always performed better than component having many props. This is always a good way to drop props that are not used inside children components. Here is an example that shows a clear picture of it.

import React from 'react'
import { render } from 'react-dom'

function Avatar(props) {
  return (
    <div className="avatar-wrapper">
      <img className="avatar-img" alt="avatar" src={props.user.image} />
      <div className="avatar-name">{props.user.name}</div>
    </div>
  )
}

const user = {
  id: 1,
  name: 'Leanne Graham',
  image: 'https://i.picsum.photos/id/237/200/300.jpg',
  username: 'Bret',
  email: 'Sincere@april.biz',
  address: {
    street: 'Kulas Light',
    city: 'Gwenborough',
    zipcode: '92998-3874',
  },
}

render(<Avatar user={user} />, document.getElementById('root'))
Enter fullscreen mode Exit fullscreen mode

In this example, <Avatar /> component only needs image and name props. So whenever other props like username, email, or address get updated then <Avatar /> component re-renders. That's leading to a performance issue in the long term. There are many ways to drop props and this is up to you how you want to drop passing props. Here is the way I do.

import React from 'react'
import { render } from 'react-dom'

function Avatar(props) {
  return (
    <div className="avatar-wrapper">
      <img className="avatar-img" alt="avatar" src={props.image} />
      <div className="avatar-name">{props.name}</div>
    </div>
  )
}

const user = {
  id: 1,
  name: 'Leanne Graham',
  image: 'https://i.picsum.photos/id/237/200/300.jpg',
  username: 'Bret',
  email: 'Sincere@april.biz',
  address: {
    street: 'Kulas Light',
    city: 'Gwenborough',
    zipcode: '92998-3874',
  },
}

render(
  <Avatar name={user.name} image={user.image} />,
  document.getElementById('root')
)
Enter fullscreen mode Exit fullscreen mode

#2 Common fixing scenarios for object & function props

React is one-way data flow down the component hierarch. So sometimes we may need to pass a function to the child component. When we're passing object and functional props to children component then we've to do one more step to avoid re-creating objects and function during re-renders. Here is an example that will better explain this concept.

import React from 'react'
import { render } from 'react-dom'

function Alert(props) {
  return (
    <div
      className="alert-wrapper"
      style={{ display: props.showAlert ? 'block' : 'none' }}
    >
      <div className="alert-close" onClick={props.handleCloseAlert}>
        X
      </div>
      <div className="alert-title">{props.alertData.title}</div>
      <div className="alert-description">{props.alertData.description}</div>
    </div>
  )
}

function App() {
  const [showAlert, setShowAlert] = React.useState(false)
  const [counter, setCounter] = React.useState(0)

  const alertData = {
    title: 'There was an error processing your request',
    description: 'Please try again...',
  }

  const handleShowAlert = () => {
    setShowAlert(true)
  }

  const handleCloseAlert = () => {
    setShowAlert(false)
  }

  const handleIncrementCounter = () => {
    setCounter(counter + 1)
  }

  return (
    <div>
      <button onClick={handleIncrementCounter}>counter: {counter}</button>
      <button onClick={handleShowAlert}>Show Me Alert</button>
      <Alert
        showAlert={showAlert}
        alertData={alertData}
        handleCloseAlert={handleCloseAlert}
      />
    </div>
  )
}

render(<App />, document.getElementById('root'))
Enter fullscreen mode Exit fullscreen mode

In this example, we've created two components <App /> and <Alert />. <App/> is a parent component where we've defined stateful logic for handling <Alert /> component. Here are some ReactDevTool profiler images for understanding whats going on.

profiler-app-0

profiler-alert-0

Whenever parent component state gets updated then children also get re-rendered and we can avoid re-renders of children component by using memo, PureComponent, or shouldComponentUpdate() method. But this is will not help you for comparing object and functional props because every time it will create a new reference for object and function. There are a couple of ways to prevent re-creating it.

For object, you need to wrap object inside React.useMemo() like below.

const alertData.= React.useMemo(() => {
    title: 'There was an error processing your request',
    description: 'Please try again...'
}, [])
Enter fullscreen mode Exit fullscreen mode

For functions, you need to wrap function inside React.useCallback() like below.

const handleCloseAlert = React.useCallback(() => {
    setShowAlert(false)
}, [])
Enter fullscreen mode Exit fullscreen mode

Second argument for React.useMemo() and React.useCallback() is array dependencies. Empty array of dependencies specific that we don't want to re recompute value for React.useMemo() and memoized callback for React.useCallback(). There may be a circumstance where we've to recompute values and memoized callbacks and that's up to you.

Here is an improved version of the above example.

import React from 'react'
import { render } from 'react-dom'

function Alert(props) {
  return (
    <div
      className="alert-wrapper"
      style={{ display: props.showAlert ? 'block' : 'none' }}
    >
      <div className="alert-close" onClick={props.handleCloseAlert}>
        X
      </div>
      <div className="alert-title">{props.alertData.title}</div>
      <div className="alert-description">{props.alertData.description}</div>
    </div>
  )
}

function App() {
  const [showAlert, setShowAlert] = React.useState(false)
  const [counter, setCounter] = React.useState(0)

  const alertData = React.useMemo(
    () => ({
      title: 'There was an error processing your request',
      description: 'Please try again...',
    }),
    []
  )

  const handleShowAlert = React.useCallback(() => {
    setShowAlert(true)
  }, [])

  const handleCloseAlert = React.useCallback(() => {
    setShowAlert(false)
  }, [])

  const handleIncrementCounter = React.useCallback(() => {
    setCounter(counter + 1)
  }, [counter])

  return (
    <div>
      <button onClick={handleIncrementCounter}>counter: {counter}</button>
      <button onClick={handleShowAlert}>Show Me Alert</button>
      <Alert
        showAlert={showAlert}
        alertData={alertData}
        handleCloseAlert={handleCloseAlert}
      />
    </div>
  )
}

render(<App />, document.getElementById('root'))
Enter fullscreen mode Exit fullscreen mode

profiler-app-1
profiler-alert-1

#3 React.memo with react-fast-compare

Using React.memo() for every component is risky because it explicitly caches the function, which means that it stores the result in memory. If you do this with too many or too big components this leads to more memory consumption. That's why you should be careful when memoizing large components.

Mostly, we can avoid re-rendering by passing limited props. if you still want to use React.memo() then firstly see if default React.memo will works for you. If it won't then understand and identify the bottleneck in your component by using ReactDevTools profiler. After all, you determined that this problem could be resolved by using deep equality checks between prevProps and nextProps.

Let's see in the example,

import React from 'react'
import { render } from 'react-dom'
import isEqual from 'react-fast-compare'

function Input(props) {
  return <input value={props.value} onChange={props.handleOnChange} />
}

const MemoizedInput = React.memo(Input, isEqual)

function App() {
  const [username, setUsername] = React.useState('')

  const handleOnChange = React.useCallback((e) => {
    setUsername(e.target.value)
  }, [])

  return <MemoizedInput value={username} handleOnChange={handleOnChange} />
}

render(<App />, document.getElementById('root'))
Enter fullscreen mode Exit fullscreen mode

Thanks for reading. I hope you like this article feel free to like, comment, or share this article with your friends.

. . . . . . . . .