Ismael Rodriguez
Ismael Rodriguez

Reputation: 339

Avoid re-render with `useCallback`

I am trying to figure out why when I click on a specific component its sibling it will render too

function CountButton({increment, count, number}) {
  console.log(`Render CountButton ${number}`)

  return <button onClick={() => increment(count + 1)}>{count}</button>
}

function DualCounter() {
  const [count1, setCount1] = React.useState(0)
  const increment1 = React.useCallback(() => setCount1(c => c + 1), [])
  const [count2, setCount2] = React.useState(0)
  const increment2 = React.useCallback(() => setCount2(c => c + 1), [])
  console.log('Render DualCounter')

  return (
    <>
      <CountButton count={count1} increment={increment1} number={1} />
      <CountButton count={count2} increment={increment2} number={2} />
    </>
  )
}

I use useCallback and I pass theses function to use avoid that in any render the functions reference will be a different reference.

Upvotes: 1

Views: 7707

Answers (1)

tmilar
tmilar

Reputation: 1921

You are seeing a re-render on the sibling <CountButton /> component, because each time you hit the button to update the counter, you are actually updating a state value in the parent component <DualCounter />, which causes a re-render on that component as well.

And since DualCounter is re-rendered, child components will re-render as well, which in this case includes both <CountButton /> elements.

A solution to prevent this, would be wrapping CountButton component with React.memo(). This will prevent a re-render on a component that didn't have any change on the props values.

Example below:

function CountButton({increment, count, number}) {
  console.log(`Render CountButton ${number}`)

  return <button onClick={() => increment(count + 1)}>{count}</button>
}

const CountButtonMemo = React.memo(CountButton)

function DualCounter() {
  const [count1, setCount1] = React.useState(0)
  const increment1 = React.useCallback(() => setCount1(c => c + 1), [])
  const [count2, setCount2] = React.useState(0)
  const increment2 = React.useCallback(() => setCount2(c => c + 1), [])
  console.log('Render DualCounter')

  return (
    <>
      <CountButtonMemo count={count1} increment={increment1} number={1} />
      <CountButtonMemo count={count2} increment={increment2} number={2} />
    </>
  )

Another solution would be not updating the DualCounter state on each change caused by events on your CountButton components, which will stop triggering unwanted re-renders on their siblings. You could handle the state directly on each CountButton component if this made sense for your app.

Alternatively, you could use a React state management tool, such as Redux, which also solves exactly this issue, by taking charge of delegating the state of your app separated from your components themselves.

Upvotes: 5

Related Questions