Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

Avoid unnecessary component rendering with memo in nextjs

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

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

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 ?

>Solution :

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.)

Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading