Search code examples
reactjsreact-hooksreact-dates

Is it possible to Memoize an unkown number of Components with hooks?


I'm trying to avoid so many child component re-renders, but don't know how to optimize the code bellow:

I have an object with a calendar looking shape like so:

{
    2019:{
        7:{
            1:{
                avaliable: false
              },
            2:{
                avaliable: true
              }
          }
    }

}

The Day Component (omitting the setAgenda logic for brevity) :

function Day(props) {
    <span style={{ color: props.avaliable ? "blue" : "red" }}>
        {props.day}
    </span>
}

The Calendar Component:

function Calendar() {
    const [agenda, setAgenda] = useState(initialAgenda);

    renderMonth = () => {
        return(
            <>
                <Day day={1}
                     avaliable={agenda["2019"]["7"]["1"].avaliable}
                     memoized={false}
                     setAgenda={setAgenda}
                />
                <Day day={2}
                     avaliable={agenda["2019"]["7"]["2"].avaliable}
                     memoized={false}
                     setAgenda={setAgenda}
                />
           </>
    }

    return(
        <>
            {renderMonth()}
        </>
    )
}

Every time I update the avaliable property of a single Day, every <Day> rendered inside the Calendar would be re-rendered. Not only that, but every time any part of the Calendar internal State updates, both <Day>'s are re-rendered too.

Experimenting with useMemo, I came up with this:

const day1 = agenda["2019"]["7"]["1"].avaliable;
const memoDay1 = useMemo(
    () => (
      <Day day={1} avaliable={day1} memoized={true} setAgenda={setAgenda}/>
    ),
    [day1]
);

So the updated Calendar component looks like this:

function Calendar() {
    const [agenda, setAgenda] = useState(initialAgenda);

    const day1 = agenda["2019"]["7"]["1"].avaliable;
    const memoizedDay1 = useMemo(
        () => (
          <Day day={1} avaliable={day1} memoized={true} setAgenda={setAgenda}/>
        ),
    [day1]);

    const day2 = agenda["2019"]["7"]["2"].avaliable;
    const memoizedDay2 = useMemo(
        () => (
          <Day day={2} avaliable={day2} memoized={true} setAgenda={setAgenda}/>
        ),
    [day2]);

    renderMonth = () => {
        return(
            <>
                {memoizedDay1}
                {memoizedDay2}
           </>
    }

    return(
        <>
            {renderMonth()}
        </>
    )
}

Now the unnecessary re-renders are gone, when I update a single day, only a single <Day> gets re-rendered and if I update the internal State of the Calendar component, no <Day> is re-rendered - the desired behavior.

On a real life scenario, creating a variable and manually memoizing every component is impossible, since it's an unknown number of days being rendered on the screen.

What would be the ideal way to do it ?


Solution

  • Use React.memo as analogue of PureComponent for functional components:

    export default React.memo(function Day({...}) { ... })
    

    Purpose useMemo is rather caching heavy calculation then preventing re-rendering.