7
import React, { useEffect, useRef, useState, memo } from 'react';

const ExpensiveComponent = memo(({ onRender }) => {
  useEffect(() => {
    onRender();
  }, [onRender]);

  return <div>Expensive component</div>;
});

export default function App() {
  const renderCountRef = useRef(0);
  const [count, setCount] = useState(0);

  const handleRender = () => {
    renderCountRef.current += 1;
    console.log('Component render count:', renderCountRef.current);
  };

  useEffect(() => {
    const id = setInterval(() => {
      setCount(c => c + 1);
    }, 2000);
    return () => clearInterval(id);
  }, []);

  return (
    <div>
      <h1>Count: {count}</h1>
      <ExpensiveComponent onRender={handleRender} />
    </div>
  );
}

This is coming automatically multiple times?

Component render count: 1

Component render count: 2

Component render count: 3

Component render count: 4

Component render count: 5

I have a dashboard with many components, and i try to pass functions as props to deeply nested children. Every time a parent re-renders, these functions are get re-created, causing memoized children to re-render unnecessarily.

2 Answers 2

4

handleRender is a new function on each App render, and so is onRender prop, so memo can't prevent ExpensiveComponent from being re-rendered.

useCallback needs to be used, it can guarantee that function reference stays the same through the component lifetime to be used as a callback and thus prevent the unnecessary re-renders.

It should be:

const handleRender = useCallback(() => {...}, []);
Sign up to request clarification or add additional context in comments.

2 Comments

What does useCallback() does?
I updated the post. It's intended exactly for this purpose. Notice that it needs [] in the list of dependencies in this case
0

The reason ExpensiveComponent keeps rendering is because you're passing a new function (handleRender) every time App re-renders. Even if the content of the function doesn't change, React sees it as a different function, so React.memo can't prevent the re-render.

To fix this, you just need to make sure the function stays the same between renders. You can do that using useCallback. It keeps the function reference stable.

const handleRender = useCallback(() => {
  renderCountRef.current += 1;
  console.log('ExpensiveComponent render count:', renderCountRef.current);
}, []);

1 Comment

How does this address the issue? Please edit your answer and explain the changes you've made to solve the problem. Code-only answers are not good answers. Furthermore, this was already provided as an answer here. Please don't duplicate answers as well.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.