3+1 React common mistakes that trigger unnecessary re-renders

Riccardo Bertolini
5 min readJun 27, 2023

--

Though we frequently use functions and components syntax in our daily coding routines, are we fully aware of how they manipulate the DOM and how many times they re-render? Let’s dive into the effective use of memoization functions, among other techniques.

Remember, there’s no one-size-fits-all solution, so thoughtful planning and evaluation are crucial. Always ask yourself: Will my component re-run, re-render, or re-initialize this function with each prop update? Or each time it’s called?

How the world looks like from your components — Photo by Patrick T'Kindt on Unsplash

Repetition is not smart

Check now this code, and try to guess why it will trigger possible re-renders:

const Pagination = ({ pagesNumbers }) => {
const augmentedValues = pagesNumbers.map(page => page.number + 2);
return <RenderPages data={augmentedValues} />;
};

It seems so innocent, right? 👼🏻

When objects or arrays are created inline within the component, they are considered new instances with every render, even if the data is the same. Always think what kind of computation we’re passing and how many times this happen in each component.

React’s useMemo hook comes to the rescue by memoizing the output of the function, telling basically to React to skip the calculation and use the value already prepared (if consistent, of course).

const Pagination= ({ pagesNumbers}) => {
const augmentedValues = useMemo(() => pagesNumbers.map(page => page.number + 2), [pagesNumbers]);
return <RenderPages data={augmentedValues} />;
};

useMemo recomputes the memoized value only when a dependency changes. However, it should not be abused, as it incurs memory cost due to storing the function's result until the dependencies change. Furthermore, useMemo also carries out dependency comparison on every render, which is another performance cost. Therefore, overusing useMemo can inadvertently lead to reduced performance instead of enhancing it.

Déjà vu: I think I’ve seen this function before 👁️

Functional component opened the doors to a magical, more readable world (in my opinion). However, there are many cases where we’re emulating the behavior of methods in Class in the function, especially if those are not pure functions and modifying the state,

const MyButton = ({ updateState }) => {
const handleClick = (e) => {
e.preventDefault();
updateState(e);
};
return <button onClick={handleClick}>Update Props</button>;
};

Defining functions within the component body leads to the creation of a new function with each render: if this function is passed as a prop, it will cause the child components to re-render unnecessarily.

const MyButton= ({ updateState}) => {
const handleClick = useCallback((e) => {
e.preventDefault();
updateState(e);
}, [onClick]);
return <button onClick={handleClick}>Update Props</button>;
};

This can be fixed using useCallback, which will return a memoized version of the callback and avoid to re-initiate the function on each component usage (and re-render). Similar to useMemo, useCallback also has a cost. The function passed to useCallback is stored in memory until its dependencies change Additionally, useCallback has to check the dependencies on each render to determine whether to return the memoized function or create a new one.

⚠️ Using anonymous functions or methods directly in JSX has the same effect as defining functions within the component — creating a new function on every render. This can lead to unnecessary re-renders.

const MyButton= () => {
return <button onClick={() => console.log("Inline function!")}>Click here</button>;
};

1,2,3… and again 1,2,3

What do you think about this piece of code?

const MyList = ({ items }) => {
return (
<ul>
{items.map((item, index) => (
<li key={index}>{item}</li>
))}
</ul>
);
};

Nothing special, just rendering some items from an array. What if
<MyList /> is appearing 2 or 3 times in the parent component?

export const MainContent = ({content, headerList, footerList}) => {
return <div>
<MyList items={headerList} />
<p>{content}</p>
<MyList items={footerList} />
</div>
}

When rendering a list of components, it’s essential to provide a unique key for each child. Using an index as a key can lead to unnecessary re-renders and potential issues with the state. In this case, each li element is given a key that corresponds to its index in the array; this works fine until the list is static, but if the list can change — if items can be added, removed, or reordered — this can lead to problems. In our case, even if it’s called multiple times.

Let’s say an item is removed from the beginning of the list. The indices for all remaining items will shift down by 1. However, because their content hasn’t changed, React will not re-render them, assuming that the components with the same keys didn’t change. This can lead to bugs, especially when your components have a state or lifecycle, because the state will be associated with the wrong component.

Instead, each list item should have a stable, unique identifier. If your data comes from a database, it might already have a unique ID:

const MyList = ({ items }) => {
return (
<ul>
{items.map((item) => (
<li key={item.id}>{item.name}</li>
))}
</ul>
);
};

Much better, right? Now each component will be connected to the virtual DOM and React will have full control on how to organize it.

Remember this: React.memo 🧠

Similar to the first case, what happen if our component is a simple render one? Maybe we’re not using static server rendering or caching, how can we avoid to repeat the calculation of something won’t change?

const DumbRenderOfText = ({ textThatWontChange}) => {
return <div>{textThatWontChange}</div>;
};
export default DumbRenderOfText;

In a similar way, but with reference of the full component, React.memo is doing exactly what we’ve seen before: if a component always renders the same result given the same props, you can wrap it in a call to React.memo for a performance boost by memoizing the result.

const DumbRenderOfText= ({ textThatWontChange}) => {
return <div>{textThatWontChange}</div>;
};
export default React.memo(DumbRenderOfText);

The magic of React.memo is that will only re-render the component if the props change! It’s bringing the exact same conditions, pros and cons, we’ve seen previously.

Photo by Lautaro Andreani on Unsplash

I hope you’ve found these insights and tips beneficial. Wishing you a joyful and productive coding journey!! 👨🏻‍💻⭐👩🏻‍💻

--

--