Optimizing React apps with function components: React.memo, useMemo, Lists (keys)

Caio Bittencourt
Apr 14, 2020 · 10 min read

Generally the biggest bottlenecks in a React application are redundant processes and poorly structured components. Today, I will share what I’ve learned by refactoring a component and making it much more efficient!

React.memo

I think it’s fair to start by saying that when using function components, they will always be re-rendered! Even if you pass the same props… In class components there are Pure Components, which are components that will only be re-rendered if their props change! Since we don’t use classes, but function components, we can use React.memo.

React.memo is a high order component that does the same thing as Pure Component except for function components. These components are called pure because they are deterministic, that is, given a prop x and a state y, the rendered JSX will always stay the same. That said, if the props remain the same (x) and the state (y) does not change, nothing outside the scope of this function will affect the result produced by it. Now, imagine a component that has an unpredictable result… How are we going to cache it? What if it was going to render something other than what we cached?

Example:

import React from 'react';
import Student from './Aluno';
import countGlobal from './SomeGlobalDinamicVars';
export const MyComponent = React.memo((props) => {
const randomLuckyNumber = Math.random();
let students = [];
fetch(`example.com.br/${props.classID}`).then(
(response) => students = response.students
);
return (
<div>
{students.map((item, index) => {
<div key={index}>
<Student data={item} />
</div>
})}
<p> Number of renders is: {countGlobal}</p>
<p> Lucky number is: {randomLuckyNumber}</p>
</div>
);
});

We cannot cache the result of this component because his result is completely unpredictable. Imagine that we used React.memo in this component. So, after the first render, its content is cached… The only way for the cached component to produce the same JSX as if it was re-rendering is:

  • The students response should always be the same as the first request / render.

And unfortunately we cannot guarantee these events, so to cache components we must have a predictable behavior.

How does React.memo know whether to use the cached value or re-render the component?

Ok… So the component needs to be pure (without side effects), but how does it check if the props have changed? By value? By reference? The comparison made in this method by default is a shallow compare. That being said, if the prop value is a scalar (number, strings…) the comparison is made by value, and if the prop is a complex types (arrays, objects, functions) is made purely by reference.

// Scalars shallow comparevar hiVar = "hi";
hiVar === "hi" // true
var numVar = 21;
numVar === 21 // true
// Objects, Arrays, Functions shallow compare{} === {} // false
[] === [] // false
var teacher = {name: "Geraldo"};
var otherTeacher = teacher;
otherTeacher.name = "Other Geraldo";
otherTeacher === teacher // truevar realOtherTeacher = {...teacher}; // Creating new object
realOtherTeacher === teacher // false

It is worth saying that if a component receives a function as a prop, you need to use the useCallback hook, so that it only declares the function again when necessary. Otherwise props will be different every render, since the function prop will always have a new reference.

If you think your component doesn’t need to check all the props or you want to do a custom check that makes sense for your component, React.memo in its second argument lets you manually compare props and decide whether the component should be re-rendered or not! Example:

React.memo(WrappedComponent, (prevProps, nextProps) => {
if (prevProps.staticProp === nextProps.staticProp) {
return true // Component will use cached value and not re-render
}
return false; // Component will re-render
});

Refactoring components to take advantage of React.memo

Knowing that components with the same props can use ​​cached value by React, you can now try to refactor your components by separating them into smaller components that can be cached for better performance. For example, imagine a very large XYZ component with multiple props. Some of them at each re-render change, others do not. There may be a way to break parts of that component into smaller, more cacheable components, thereby preventing parts of the XYZ component from being re-rendered without reason.

When SHOULD I use React.memo

  • Pure components (given a prop x and state y, the result will always be z)

When should i NOT use React.memo

This method is very effective in avoiding unnecessary rendering, however there are cases where React.memo can get in the way or is not recommended. For example:

  • Components that often receive different props. Since comparisons will be performed on every re-render and will almost always result on the re-rendering of the component. In this case, it is not worth the cost of comparisons (previousProps vs nextProps)!

Lists and Keys

Often when dealing with lists in React, things can get complicated, since many times our iterated data does not have an identifier. When it comes to lists React uses the key to help identify which item from a list has been modified, added or removed. According to React, keys are used to:

identification for managing the states of the multiple components.

Performance in this case would be just a consequence. The key must be unique only between siblings and static for each item on the list.

The key is a reserved word for React, when passing it to a component react already assigns that key to the component… You don’t need to pass it on to a div for example. If this component has a prop called key it will always be undefined!

Ideal scenario for keys

The best scenario for assigning keys is to use some unique identifier for the object being iterated.

Example:

function PersonList(props) {
const persons = [
{nome:"Ana", id:"abcde"},
{nome:"Caio", id:"xyz"}
];
return (
<ul>
{persons.map((person, i)
<ListItem key={person.id} value={person.nome} />
)}
</ul>
);
}

If there is no unique identifier you can try to combine two values ​​to generate a unique value (if there is no single value alone)!

For example:

function PersonList(props) {
const persons = [
{nome:"Ana", line:"Harry Potter", ticketNumber: 1},
{nome:"Caio", line:"Harry Potter", ticketNumber: 2},
{nome:"Felipe", line:"Marley & Me", ticketNumber: 1}
];
return (
<ul>
{persons.map((person, i)
<ListItem
key={`${person.line}|${person.ticketNumber}`}
value={person.nome} />
)}
</ul>
);
}

Since two people will never have the same ticket number and the same movie, its safe to say that we can use the line + ticket number to generate a unique key.

I have no way of identifying uniquely, what do I do?

But not everything is wonderful … Many times we don’t have this hand-kissed attribute to be used :/. However, what can be done is, in the first contact with the array of people, assign an id to each person, so that this identifier does not change, that is, once Caio is assigned the “xyz” identifier, it must be kept between re-renders. And every new person who joins the list must be given a unique identifier! For example:

import React from 'react';
import { generate } from 'shortid';
const MyComponent = (props) => { const [personsWithIds, setPersonsWithIds] = React.useState(); React.useEffect(() => {
const students = fetch(`example.com/classes/${props.classID}`);
setPersonsWithIds(students.map(student =>
{...student, identifier: generate() }
);
)} , [props.classID]);
const addStudent = () => {
setPersonsWithIds((prevState) => ([
...prevState, {nome: 'Caio', identifier: generate()}
]))
};
return (
<div>
{personsWithIds.map((item, index)
<div key={item.identifier}>
<Person data={item} />
</div>
)}
<button onClick={addStudent} />
</div>
);
}

Imagine that the example above represents a school where a student can only belong to one class. In the component above, students will always have a unique and static identifier. Because the component will only search for students again if the class id changes. But even if it changes, the students will be from another class, so new identifiers must be generated because they will be different students. And these identifiers will be saved with state, so they will persist between renders. The important thing is to find a way to keep the identifiers always static, so that if a student Ana received an identifier it will always be the same for her. If a new student enters a class, we must generate a new identifier.

Example of what you SHOULDN’T do:

import { generate } from 'shortid';const MyComponent = (props) => {return (
<div>
{props.persons.map((item, index)
<div key={generate()}>
<Person data={item} />
</div>
)}
</div>
);
}

Here in the example above, it always generates a new key… With that, the rendering of the component is extremely slow, because besides the processing of generating a key, this new key will always be unknown to React, and as consequence react will have to render all divs again instead of reusing existing components. And this is not very efficient.

Using index as key

It is important to remember that if an iteration does not specify a key, React by default will use the index as the key!

Example:

function PersonList(props) {
const persons = props.persons;
return (
<ul>
{persons.map((person, i)
<ListItem key={i} value={person} />
)}
</ul>
);
}

Here in this case, we are using the array index as the key for each item in the list. If this array NEVER changes his order, this is a very good solution. However, this approach is not recommended if the array allows items to change order. This is because if the items change their order, their index will be changed and consequently their key will change. With that, the old reference of the index that React had assigned to that item is lost. And when that happens React can cause unwanted side effects.

Imagine that:

const persons = ["Ana", "Pedro", "Caio"];// Persons array initially:
<ListItem key={0} value={Ana} />
<ListItem key={1} value={Pedro} />
<ListItem key={2} value={Caio} />
// Remove Ana resulting list:
<ListItem key={0} value={Pedro} />
<ListItem key={1} value={Caio} />

Above we can observe that when we remove Ana, Pedro who was index 1, became index 0. And Caio who was 2, became 1. Their keys changed, and consequently React confuses the elements and depending on the case may even have unexpected visual results!

Hmmmm, but I put an index as my key and there is nothing strange … Shouldn’t it have bugged?

In this topic I will explain how the side effect of using indexes as keys works. I already said that if Ana was once index 0 and left the list leaving Caio as index 0, Caio will reuse Ana’s old component. That said, the components that will have visual bugs when reusing keys are those that manage some kind of state!

Imagine that the React cache looks like this for the ListItem component:

<ListItem key={0}  value={'Ana'} />ListItem  key = 0  props = {value: 'Ana'}  state = { age: null }<ListItem key={1}  value={'Caio'} />ListItem  key = 1 props = {value: 'Caio'}  state = { age: null }

Now, imagine that Ana has filled on her component her age (which for some reason is managed by the state)

Now we have:

<ListItem key={0}  value={'Ana'} />ListItem  key = 0  props = {value: 'Ana'}  state = { age: 19}<ListItem key={1}  value={'Caio'} />ListItem  key = 1 props = {value: 'Caio'}  state = { age: null }

When removing Ana from the list, the person who will occupy index 0 and therefore key 0 will be Caio. And consequently will inherit the memoized component (with the age set in the state) from Ana!

Now we have:

<ListItem key={0}  value={'Caio'} />ListItem  key = 0 props = {value: 'Caio'}  state = { age: 19 }ListItem  key = 1 props = [to be sent] state = { age: null }

Caio will have received the status of Ana’s old component, but with the updated props, and with that, the visual inconsistencies begin! The props will always be updated because they are passed to Ana’s old component, but the state stays the same!

useMemo

This hook, like React.memo, can make a huge difference in your application. Unlike useCallback which saves a function declaration (reference), useMemo is a hook that allows you to save the result of a function!

Exemplo:

// Executes doHeavyComputation when a or b change.
const memoisedValue = React.useMemo(
() => doHeavyComputation(a, b)
, [a, b]);
// Executes doHeavyComputation only once and uses memoized result on next renders.
const memoisedValue = React.useMemo(
() => doHeavyComputation(a, b)
, []);
// Always executes doHeavyComputation
const memoisedValue = React.useMemo(
() => doHeavyComputation(a, b));

In the example above we see the useMemo signature. In the first argument you must use a function without arguments and in the return what you want to be memoized (cached). In the second argument an array of dependencies, where if any of those variables are changed doHeavyComputation will be executed again in the next render. If an empty array is passed as the second argument, the function executes once and then uses the memorized value. And if you omit the second argument, the function will always be executed in all renders!

The useMemo hook is generally used for the following purposes:

  • Heavy processing operations
const MyComponent = (list, query) => {
// Will be recalculated in every render
const filteredList = heavyFilterArray(list, query);
// Will recalculate only when list ou query changes.
const memoizedFilteredList = React.useMemo(
() => heavyFilterArray(list, query),
[list, query],
);
}
  • Prevent child components rendering
const MyParentComponent = (props) =>  {
const childOne = React.useMemo(() => <CHild item={props.name} />, [props.name]);
return (
<>
{childOne}
</>
)
}
  • Avoid rendering jsx or static components (use it only on more complex / jsx components)
const MyComponent = (props) =>  {
const staticChild = React.useMemo(() => <Filho item={'Informação estática aqui'} />, []);
const jsxEstatico = React.useMemo(() => (
<div> textHere </div>
), []);
return (
<>
{staticChild}
{jsxEstatico}
</>
)
}

When NOT to use useMemo

  • Small components or light processing operations

Conclusion

These tools can prove to be very useful when it comes to performance. However, it is worth remembering that a component is often not performatic due to the way it is structured, and simply putting a useMemo or React.memo will only mitigate the real problem. Before adopting solutions like these, find out if your component has wasted renders. If the component is really complex, these methods will help a lot in optimizing your components!

hurb.labs

hurb.com’s geekiest blog. reshaping how we travel

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store