skip to Main Content
const Parent = () => {
    let [hitCounter,setHitCounter] = React.useState(0);
    let [clickCounter,setClickCounter] = React.useState(0); 
    const data= React.useMemo(() => {
        console.log("in useMemo")
        return hitCounter
    },[hitCounter]);
    return(
        <>
        <Child hitCounter={data}/>
        <input type="button" onClick={() => setHitCounter(hitCounter + 1)} value="Hit me"/>
            {hitCounter}
            <div><input type="button" onClick={() => setClickCounter(clickCounter + 1)} value="Click me"/>
            {clickCounter}</div>
        </>
    )
}

export default Parent;

const Child = (props) => {
    let data = 0;
    useEffect(() => {
        console.log("in useeffect");
        data = props.counter;
    },[props.counter])
    console.log("in  Child");
    return(
        <div>
            The Hit counter value is {data}
            </div>
    )
}

export default Child

In the Parent component, I have 2 buttons Hit me and Click me. On click of each of them the respective counter gets incremented. So when I am clicking on ‘Click me’ the clickCounter gets incremented but hitCounter remains same. I am sending the memoized ‘Hit me’ count(hitCounter) to the child.

My confusion is why the child component is still getting re-rendered when clickCounter changes even though I am not passing it to child and have used useMemo for hitcounter(this is not getting changed).

I am using useMemo for hitcounter to make sure the child does not gets re-rendered when hitcounter is not changing.

2

Answers


  1. In response to a change in state, React will re-render that component with the changed state, and all children of that component. The props you pass to those children do not matter.

    Consider this example:

    
    export function App() {
      const [count, setCount] = useState(0);
    
      return (
        <div>
          {count}
          <button onClick={() => setCount(count + 1)}>Increment</button>
          <Child />
        </div>
      );
    }
    
    function Child() {
      console.log('foo');
      return <>Child</>;
    }
    

    Here every time you click the button, 'foo' will get logged to the console. Here the App component has updated state, so all it’s children render, too.


    If you don’t want that, you can use React.memo on the component, not the state.

    Wrap a component in memo to get a memoized version of that component. This memoized version of your component will usually not be re-rendered when its parent component is re-rendered as long as its props have not changed.

    This is not a hook, it’s a wrapper around component declarations.

    const Child = React.memo(() => {
      console.log('foo');
      return <>Child</>;
    })
    

    With that change, the Child component will only render once for it’s lifetime since it props could never change.

    See Stackblitz


    That said, you rarely need to do this. You should only be looking at this if yo are having actual performance problems in your app. And even if you are, in my experience it’s usually more due to your business logic than react’s rendering performance.

    Login or Signup to reply.
  2. If you want to skip rendering a child component, you need to memoize that component itself. For example:

    import React, { memo } from 'react';
    
    const Child = memo((props) => {
     // ... same implementation as before
    })
    

    React.memo will skip rendering the child if none of its props have changed.

    The other option is to memoize the element in the parent:

    const Parent = () => {
      const [hitCounter,setHitCounter] = React.useState(0);
      const [clickCounter,setClickCounter] = React.useState(0); 
    
      const childElement = useMemo(() => {
        return <Child hitCounter={hitCounter}/>
      }, [hitCounter])
    
      return (
        <>
          {childElement}
          <input type="button" onClick={() => setHitCounter(hitCounter + 1)} value="Hit me" />
          {hitCounter}
          <div>
            <input type="button" onClick={() => setClickCounter(clickCounter + 1)} value="Click me" />
            {clickCounter}
          </div>
        </>
      );
    }
    

    As long as hitCounter does not change, this will reuse the same element, and thus react knows it has already rendered that element.


    P.S, your existing code has some things that don’t do anything of note. For example:

    const data= React.useMemo(() => {
      console.log("in useMemo")
      return hitCounter
    },[hitCounter]);
    

    This code says that if hitCounter changes, it should run the memo function. That function simply returns hitCounter and assigns it to data. The same could be done with the code const data = hitCounter`.

    let data = 0;
    useEffect(() => {
      console.log("in useeffect");
      data = props.counter;
    },[props.counter])
    

    This effect does nothing of note. Effects run after rendering is complete, so nothing is going to use data. You’ve already finished rendering the component using 0 for data. You probably want:

    const Child = (props) => {
      return (
        <div>
          The Hit counter value is {props.hitCounter}
        </div>
      )
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search