Home > other >  Avoid unnecessary component rendering with memo in nextjs
Avoid unnecessary component rendering with memo in nextjs

Time:05-21

I'am trying to understand react's behaviour throught nextjs. I have an index.js page with one component Homecard displayed three times and one button that increment a value.

Each time I click on button all Homecard components are re-render.

index.js

import { Homecard } from '../components/Homecard'
import { useState } from 'react'

export default function Home() {

    const [count, increment] = useState(0);

    const homecards = [
        {
            "main": "main0",
            "sub": "sub0",
            "desc": "Desc0",
            "nav": [{
                "href": "/page0",
                "value": "see"
            }]
        },
        {
            "main": "main1",
            "sub": "sub1",
            "desc": "Desc1",
            "nav": [{
                "href": "/page1",
                "value": "see"
            }]
        },
        {
            "main": "main2",
            "sub": "sub2",
            "desc": "Desc2",
            "nav": [{
                "href": "/page2",
                "value": "see"
            }]
        }
    ];

    const handleCount = () => {
        increment(count => count   1);
    }

    return (
        <>
            <div className='d-flex justify-content-between' style={{ marginLeft: '-1.5rem' }}>
                {
                    homecards.map((homecard, index) => (
                        <Homecard
                            key={index}
                            main={homecard.main}
                            sub={homecard.sub}
                            desc={homecard.desc}
                            nav={homecard.nav}
                        />
                    ))
                }
            </div>
            <button onClick={handleCount}>increment {count}</button>
        </>
    )
}

homecard.js

export default function Homecard({ main, sub, desc, nav }) {
    console.log('render Homecard');
    return (
        <div className={`${styles.homecard}`}>
            <div>
                <h3>
                    {main}
                {sub &&
                    <span>{sub}</span>
                }
                </h3>
                <p>{desc}</p>
                {nav &&
                    <ul>
                        {nav.map((link, index) => (
                            <li key={index}>
                                <Link href={link.href}>
                                    <a>{link.value}</a>
                                </Link>
                            </li>
                        ))}
                    </ul>
                }
            </div>

        </div>
    )
}

I tried to wrap my Homecard with React.memo like so

const Homecard = React.memo(({ main, sub, desc, nav }) => {})

But I still see console.log('render Homecard'); when my button is clicked. How can I could update only my button and not others components ?

CodePudding user response:

The problem is that you're recreating your homecards array on every render of Home, so each nav object is a new object, and so React.memo sees a difference in the props and doesn't optimize away the subsequent re-renders.

There are a couple of ways to fix it:

  1. Define homecards outside Home; it's unchanging, so there's no reason to recreate it every time Home is called.

  2. If you couldn't do that for some reason, you could pass a second argument to React.memo, the "areEqual" function, which will receive the old props and the new ones; in the function, do a deep comparison to see if if nav objects have the same property values are the previous ones even though they're different objects.

Example of #1:

const { useState } = React;

/*export default*/ const Homecard = React.memo(({ img, main, sub, desc, nav }) => {
    console.log('render Homecard');
    return (
        <div className={`${""/*styles.homecard*/}`}>
            <div>
                <h3>
                    {main}
                {sub &&
                    <span>{sub}</span>
                }
                </h3>
                <p>{desc}</p>
                {nav &&
                    <ul>
                        {nav.map((link, index) => (
                            <li key={index}>
                                <a href={link.href}>
                                    {link.value}
                                </a>
                            </li>
                        ))}
                    </ul>
                }
            </div>

        </div>
    )
});

const homecards = [
    {
        "main": "main0",
        "sub": "sub0",
        "desc": "Desc0",
        "nav": [{
            "href": "/page0",
            "value": "see"
        }]
    },
    {
        "main": "main1",
        "sub": "sub1",
        "desc": "Desc1",
        "nav": [{
            "href": "/page1",
            "value": "see"
        }]
    },
    {
        "main": "main2",
        "sub": "sub2",
        "desc": "Desc2",
        "nav": [{
            "href": "/page2",
            "value": "see"
        }]
    }
];

/*export default*/ function Home() {

    const [count, increment] = useState(0);

    const handleCount = () => {
        increment(count => count   1);
    }

    return (
        // Sadly, Stack Snippets don't support <>...</>
        <React.Fragment>
            <div className='d-flex justify-content-between' style={{ marginLeft: '-1.5rem' }}>
                {
                    homecards.map((homecard, index) => (
                        <Homecard
                            key={index}
                            main={homecard.main}
                            sub={homecard.sub}
                            desc={homecard.desc}
                            nav={homecard.nav}
                        />
                    ))
                }
            </div>
            <button onClick={handleCount}>increment {count}</button>
        </React.Fragment>
    )
}


const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<Home />);
<div id="root"></div>

<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.1.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.1.0/umd/react-dom.development.js"></script>

(Warning: I changed a couple of minor things to make this work in Stack Snippets, like replacing Link and using React.Fragment instead of <>...</>, so don't copy and paste it directly back into your project, just move homecards in your existing code.)

  • Related