37

I just started experimenting with React hooks and I'm wondering how I can prevent a child component from re-rendering when it's parent re-renders. I'm looking for something similar to returning false in componentDidUpdate. My issue seems to stem from the click handler I'm calling in the child component to change state in the parent component. Since the function is created in the parent component, it is created new on each parent render which triggers a prop change in the child component, which then causes the child to re-render (I think). Here is some sample code to help illustrate the situation.

function Parent() {
    const [item, setItem] = useState({ name: "item", value: 0 });

    const handleChangeItem = () => {
        const newValue = item.value + 1;
        setItem({ ...item, value: newValue });
    };

    return <Child item={item} changeItem={handleChangeItem} />;
}

const Child = React.memo(function Child({ item, changeItem }) {
    function handleClick(){
        changeItem();
    }
    return (
        <div>
            Name: {item.name} Value: {item.value}
            <button onClick={handleClick}>change state in parent</button>
        </div>
    );
});

How do I prevent Child component from rendering every time Parent component renders? Should handleChangeItem in the parent live someplace else so that it is not re-created on each render? If so, how does it get access to item and setItem returned by useState?

I'm pretty new to react and just started playing with hooks so I'm probably missing something obvious.

2 Answers 2

28

In your case it doesn't really makes sense to memoize Child because if item changes, the child has to re-render. However if there is a case that props do not change , but still the child is re-rendering due to the functions getting recreated you would make use of useCallback hook to memoize the functions, on each render. Also since you have memoized the handler, you should make use of the callback method to update state since item inside the handler will only refer to the value it had when the function was initially created

function Parent() {
  const [item, setItem] = useState({ name: "item", value: 0 });

  const handleChangeItem = useCallback(() => {
    setItem(prevItem => ({ ...prevItem, value: prevItem.value + 1 }));
  }, []);

  return (
    <>
      Name: {item.name} Value: {item.value}
      <Child changeItem={handleChangeItem} />
    </>
  );
}

const Child = React.memo(function Child({ item, changeItem }) {
  function handleClick() {
    changeItem();
  }
  console.log("child render");
  return (
    <div>
      <button onClick={handleClick}>change state in parent</button>
    </div>
  );
});

Working demo

P.S. Credit to @danAbramov for the direction

Sign up to request clarification or add additional context in comments.

7 Comments

This answers the question although I ended up using useReducer with dispatch passed via context (useContext). This is the preferred approach and is clearly described in the hooks docs/faq.
This is the perfect answer, Awesome!!
this approach causes performance fallback otherwise using memo would be default in all hooks, which makes me thinks how you solve this problem when you dumped the simple solution like componentDidUpdate to adapt hooks.
@Jalal There is a similar API to memo called as PureComponent for classes which can be used to prevent unncessary rerendering but needs to be used judicously according to your use case since the expense for props comparison may overweigh the expense of re-rendering. Also useCallback is a way to memoize functions to avoid closure problems which is why react in the first place provides it. It should be used when the state and state updaters are not complex else you can go ahead with using useReducer
@Jalal You can use memo and implement the same logic with functional component too. and ofCourse there are lots of things that are debatable but as far as my experience goes with Hooks, it has a steep learning curve but is extremely cool once you get along with it and all your related logic is at one place
|
5

Shubham Khatri accurately answered the original question but I am adding this answer to point to the recommended way of avoiding this callback problem.

From the docs:

useReducer is usually preferable to useState when you have complex state logic that involves multiple sub-values. It also lets you optimize performance for components that trigger deep updates because you can pass dispatch down instead of callbacks.

https://reactjs.org/docs/hooks-reference.html#usereducer

From the FAQ:

In large component trees, an alternative we recommend is to pass down a dispatch function from useReducer via context...

https://reactjs.org/docs/hooks-faq.html#how-to-avoid-passing-callbacks-down

The key to all of this is that dispatch never changes, unlike callbacks which are created every render.

Comments

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.