Sitemap
Sitaram Mulik

Sitaram Mulik

Web developer

Tackling wasted renders

11 min readApr 11, 2025

--

Wasted (unnecessary) renders is one of the primary reasons behind a slowness of React.js application. In this article we will be looking at the different coding styles which causes wasted renders and what are the better alternatives which we should follow to gain improvement in the application performance.

Understanding wasted renders

Wasted renders occur when a component in the application is re-rendered without any actual changes being made to the virtual DOM. The virtual DOM is a lightweight copy of the actual DOM, and it’s a crucial part of how libraries like React.js update the user’s view in a highly efficient manner. When a component is rendered without any changes in the virtual DOM, it means that the process consumes resources — such as memory and CPU time — without providing any benefit. This can lead to sluggish performance and a poor user experience, especially in complex applications.

Why wasted renders happen

Wasted renders can occur for several reasons. One common cause is improper use of state or props within components. For instance, if a component’s state or props change in a way that doesn’t actually affect the output of the render function, but still triggers a re-render, this can lead to inefficiency. Additionally, poor structuring of components or excessive use of certain lifecycle methods can result in wasted renders.

Common coding styles which causes wasted renders

Coding style 1: Passing new references of variables as a props

Take a look at the below example. We need to pass mapped list of items to the MailList component/ One (wrong) way to do it by mapping the items right where we are passing them as a prop (line no 9) to MaiList component. This approach causes wasted rendering of the MailList component.

function Mail({ items }) {
const [enablePreview, setEnablePreview] = useState(false);

return (
<>
<button onClick={() => setEnablePreview((previewState) => !previewState)}>
Toggle preview
</button>
<MailList items={items.map((item) => ({ ...item, isMail: true }))} />
</>
);
}

const MailList = memo(({ items }) => {
return items.map((item) => (
<div key={item.id}>
<MailListItem item={item} />
</div>
));
});

const MailListItem = memo(({ item }) => {
return <div>{item.subject}</div>;
});

Please check this stackblitz example.

This is why its bad coding style -

  1. In the above example, along with the MailList component the Mail component also contains a a “Toggle preview” button using which user can toggle the preview mode.
  2. Important thing to note here is that logically this state update should not have any impact on the MailList component since they both are unrelated. However because of the coding style we have adopted, every time the user clicks the button to toggle preview mode, it also causes rendering of the MailList component as well which is totally unnecessary.
  3. This happens because every time user clicks on the preview button, it updates the state of the Mail Component and triggers the rendering of Mail component. During the rendering of the Mail component the map expression is also executed. Javascript map function always returns a new reference which is being passed to the MailList component. And this is how unnecessarily the rendering of MailList component also triggered. These are the wasted renders for the MailList component.

Other indirect ways which can lead to same problem

a] Example of creating new references of props through HOC components.

function Mail({ items }) {
const [enablePreview, setEnablePreview] = useState(false);
return (
<>
<button onClick={() => setEnablePreview((previewState) => !previewState)}>
Toggle preview
</button>
<MailList items={items.map((item) => ({ ...item, isMail: true }))} />
</>
);
}

const MailList = withMappedItems(
memo(({ items }) => {
return items.map((item) => (
<div key={item.id}>
<MailListItem item={item} />
</div>
));
})
);

const MailListItem = memo(({ item }) => {
return <div>{item.subject}</div>;
});

function withMappedItems(BaseComponent) {
return function Consumer(props) {
const mappedItems = props.items.map((item) => ({ ...item, isMail: true }));

return <BaseComponent {...props} items={mappedItems} />;
};
};

Stackblitz example — https://stackblitz.com/edit/vitejs-vite-13zakz?file=src%2FMail.jsx

b] Example of returning new reference of variable from react hook

function Mail({ items }) {
const [enablePreview, setEnablePreview] = useState(false);
const mappedItems = useMappedItems(items);

return (
<>
<button onClick={() => setEnablePreview((previewState) => !previewState)}>
Toggle preview
</button>
<MailList items={mappedItems} />
</>
);
}

const MailList = memo(({ items }) => {
return items.map((item) => (
<div key={item.id}>
<MailListItem item={item} />
</div>
));
});

const MailListItem = memo(({ item }) => {
return <div>{item.subject}</div>;
});

const useMappedItems = (items) => {
const mappedItems = items.map((item) => ({ ...item, isMail: true }));

return mappedItems;
};

stackblitz example — https://stackblitz.com/edit/vitejs-vite-muyqzg?file=src%2FMail.jsx

Solution — Avoid creating new references of variables using useMemo hook

The problem in the first example can be easily fixed by memorising the value of mapped items using the useMemo hook.

function Mail({ items }) {
const [enablePreview, setEnablePreview] = useState(false);
const mappedItems = useMemo(() =>
items.map((item) => ({ ...item, isMail: true })),
[items]);

return (
<>
<button onClick={() => setEnablePreview((previewState) => !previewState)}>
Toggle preview
</button>
<MailList items={mappedItems} />
</>
);
}

Similar to this we can use this hook inside a HOC function as well like this

function withMappedItems(BaseComponent) {
return function Consumer(props) {
const mappedItems = useMemo(() =>
props.items.map((item) => ({ ...item, isMail: true })),
[props.items]);

return <BaseComponent {...props} items={mappedItems} />;
};
};

Coding style 2: Passing new references of functions as a props

Functional components creates new references of the function inside component overtime they are rendered. If we pass these functions directly to child components then it will create similar issue like in above examples. Take a look at below code, which causes unnecessary re-rendering of the MailList and MailListItem components -

function Mail({ items }) {
const [enablePreview, setEnablePreview] = useState(false);

return (
<>
<button onClick={() => setEnablePreview((previewState) => !previewState)}>
Toggle preview
</button>
<MailList items={items} />
</>
);
}

const MailList = withMailModifiers(
memo(({ items, transformMail }) => {
return items.map((item) => (
<div key={item.id}>
<MailListItem item={item} transformMail={transformMail} />
</div>
));
})
);

const MailListItem = memo(({ item }) => {
return <div>{item.subject}</div>;
});

function withMailModifiers(BaseComponent) {
return function Consumer(props) {
const transformMail = (mail) => ({
...mail,
subjectLength: mail.subject.length,
});

return <BaseComponent {...props} transformMail={transformMail} />;
};
}

This is why its bad coding style -

  1. The unrelated state update due to the “Toggle Preview” button click triggers rendering of the MailComponent. MailList component is child of the MailComponent hence it will also get rendered.
  2. The withMailModifiers HOC is wrapped around a memorised MailList component.
    Note that if one can easily include the HOC inside React.memo to avoid the unnecessary re-rendering of the MailList component, however since HOCs can also be used as annotations along with class components, we can easily end up in this situation.
  3. Whenever the HOC is rendered it creates a new reference of the transformMail function. The function is passed to the BaseComponent which in our case is the MailList component.
  4. Further the transformMail function is also passed to the MailListItem component to make matter worse. This leads to unnecessary rendering of all occurrences of the MailListItem component as well.

Solution — Avoid creating new references of functions using useCallback hook.

We should wrap the function which are being passed to the children inside a useCallback hook no matter where those are defined. The problem occurred due to transformMail function which was defined inside a HOC can be fixed by wrapping it with useCallback like below -

function withMailModifiers(BaseComponent) {
return function Consumer(props) {
const transformMail = (mail) => ({
...mail,
subjectLength: mail.subject.length,
});

return <BaseComponent {...props} transformMail={transformMail} />;
};
}

Coding style 3: Creating derived state from redux state in useSelector callback

We often need to transform redux state collected by the useSelector hook and one way to achieve it is as shown in below example

function Mail() {
const mails = useSelector(state => {
let result = [...state.items];
// do some stuff
return result;
});

return <MailList items={mails} />;
}

The above coding style will cause wasted rendering every time any action in redux is dispatched.

Get Sitaram Mulik’s stories in your inbox

Join Medium for free to get updates from this writer.

This is why its bad coding style -

  1. The code does executes the transformation logic at the time of component rendering but the issue with it is that every time any action in redux dispatched, that logic will get executed.
  2. It will return new reference of the result and eventually will cost wasted renders.
  3. Hence the expensive logic written inside the useSelector callback function can lead to wasted renders and can cost application performance.

Solution — Memorise state derived from the redux state

To solve the wasted renders caused by derived state of redux we can make use of useMemo hook to memorise the derived state. here is how -

function Mail() {
const items = useSelector(state => state.items);
const mails = useMemo(() => {
let result = [...state.items];
// do some stuff
return result;
}, [items]);

return <MailList items={mails} />;
}

While this approach is far better than the older approach and it does avoid wasted renders, redux recommends using the createSelector api from the reselect library to memorise the selector logic itself.

The main advantage of using createSelector api is that it will return the result at render time unlike the above example. Additionally we can use the memorised result returned by the selector across different components which makes it reusable.

More information regarding createSelector api can be found here —

Coding style 4: Improper use of keys with list of components

If multiple components are listed in list form in react/preact, it is important to assign keys to them.

The main benefit of using keys is optimisation of performance. Keys help react/preact efficiently update and render only the components that have changed. Without proper keys, react/preact will have to re-render all components in a list every time state is changed, leading to performance issues. For example if any item is added or removed from a list of 100 items, without unique keys its not possible to detect which one is added or removed. Then the only option remains is to re-render all 100 components.

Rule of using keys — Each key must be unique for each component but at the same time it must remain same once it is assigned throughout the lifecycle of the component. The second part of the above sentence is often misunderstood which leads to the coding styles like below.

Listing down below some common coding styles which are incorrect and defeats the purpose of using keys

a] Making use of index as keys -

export default function TodoList() {
return (
<input
placeholder="add todo"
onChange={(e) => setNewTodo(e.target.value)}
/>
<ul>
<button onClick={addItem}>Add</button>
{list.map((item, index) => (
<li key={item.id + index}>
<TodoListItem item={item} />
<button onClick={() => removeItem(item.id)}>Remove</button>
</li>
))}
</ul>
);
}

id of the item might be unique database identifiers, but once we concat it with the index, it will change on add or remove operation.

b] Generating unique keys -

import { v4 as uuidv4 } from 'uuid';

export default function TodoList() {
const [list, setList] = useState([]);

useEffect(() => {
fetch('some-url').then((res) =>
res.json().then((data) => setList(data));
);
}, []);

return (
<input
placeholder="add todo"
onChange={(e) => setNewTodo(e.target.value)}
/>
<ul>
<button onClick={addItem}>Add</button>
{list.map((item) => (
<li key={uuidv4()}>
<TodoListItem item={item} />
<button onClick={() => removeItem(item.id)}>Remove</button>
</li>
))}
</ul>
);
}

Even though the uuidv4 returns unique identifier, it will change whenever the TodoList component render. Since it will make a call to the uuidv4 function every time it renders which will return a new value.

This is why these are bad coding styles -

  1. Check this live example — https://stackblitz.com/edit/vitejs-vite-2pp6wg
  2. Remove the first item from the list and check the logs to see how much time the click event listener has taken. It’s more than 1 second!
  3. Once we remove the item, list state will be updated and will trigger the rendering of the TodoList component which will also call the list.map function.
  4. Note that we have already removed the first element which had index 0 concatenated in the key. So during this render the 0 index will be vacant and will be assigned to the 2nd component of the original list (even though it’s in concatenated form it still will be different than earlier one). Similarly the index 1 will be assigned to the 3rd element and so on. This cause react/preact to render all the components which leads to the performance issue.

Solution — Use unique identifiers stored in db

Ideally we must use database unique identifiers as keys. The other solutions will always be a workarounds.

Make sure the keys are not changed on every render. One way to do is to assign keys where we receive the data from database, like below.

import { v4 as uuidv4 } from 'uuid';

export default function TodoList() {
const [list, setList] = useState([]);

useEffect(() => {
fetch('some-url').then((res) =>
res.json().then((data) => setList(data.map(item => ({
...item,
key: uuidv4()
})));
);
}, []);

return (
<input
placeholder="add todo"
onChange={(e) => setNewTodo(e.target.value)}
/>
<ul>
<button onClick={addItem}>Add</button>
{list.map((item) => (
<li key={item.key}>
<TodoListItem item={item} />
<button onClick={() => removeItem(item.id)}>Remove</button>
</li>
))}
</ul>
);
}

Note that for all of the above solutions which are related to the enablePreview state, I am assuming that it is required to have the enablePreview state inside the Mail Component itself. Otherwise we should think of moving the state locally to the relevant child component which will avoid this problem. For example in our case we can create a Preview child component and move the enablePreview state inside that component so that it doesn’t affect the other child components of Mail component.

Impact of wasted renders on UI performance

  1. Imagine a scenario where we are showing 100 mails on UI.
  2. In all of the above implementation, each time user toggles the preview mode, the reference of the items props will change.
  3. This causes the unnecessary rendering of MailList component.
  4. The bigger problem is that it also causes the unnecessary rendering of the MailListItem component. Thats because we are returning new reference of every item object present in the items array from the map function.
  5. So all the 100 occurrences of the MailListItem component will be re-rendered on one small action which has nothing to do with MailList or MailListItem components.
  6. Now in the above example the MailListItem is very simple and light component since its only rendering a text. However in a complex application the component can be filled with a heavy logic which depends on the item prop. Logics such as accessing and processing the item properties and conditional rendering of html elements based on the property values of item object. The UI will also most likely contain much more html elements than above example. This situation can slow down your application significantly.
  7. Take a look at the below example where we are calling the complexOperations function for subject and body properties. It basically just iterates the string characters and adds an empty log for each character present. Any guesses how much time does the Mail component require to render itself completely along with the updated state ? It’s around 2.3 second! And it’s obviously because of the number of MailListItem components are getting rendered along with it and the slightly complex logic present inside them.
import React, { useState, useEffect, useMemo, memo } from 'react';

export default function Mail({ items }) {
const [enablePreview, setEnablePreview] = useState(false);
const mappedItems = items.map((item) => {
return {
id: item.id,
subject: item.title,
body: item.body,
};
});

return (
<>
<button
onClick={() => {
setEnablePreview((previewState) => !previewState);
}}
>
Toggle preview
</button>
<div>Preview mode is {enablePreview ? 'on' : 'off'}</div>
<MailList items={mappedItems} />
</>
);
}

const MailList = memo((props) => {
return props.items.map((item) => (
<div key={item.id}>
<MailListItem item={item} />
</div>
));
});

const MailBody = ({ body }) => {
return <div className="mail-body">{body}</div>;
};

const MailListItem = ({ item: { subject, body } }) => {
complexOperation(subject);
complexOperation(body);
return (
<div className="mail-list-item">
<div>subject: {subject}</div>
<MailBody body={body} />
</div>
);
};

const complexOperation = (text) => {
let result = [];
text.split('').forEach((char) => {
console.log('');
result.push(char);
});
};

StackBlitz example — https://stackblitz.com/edit/vitejs-vite-wetgbp?file=src%2FMail.jsx

What happens when we wrap the result of mapped items with useMemo hook ? The same component takes around 1ms to render. Thus following incorrect coding style cost us 2300 times slowness in one simple action.

Conclusion

In a Reactjs application, often developers can unknowingly use a coding style which can produce wasted renders by passing unnecessary new references of props to the component. This can cost application performance specially if the component occurs multiple times in the UI and contains complex logic. The best way to tackle such scenarios is to make use of react hooks like useMemo and useCallback along with React.memo in such scenarios.

--

--

Sitaram Mulik

Written by Sitaram Mulik

0 followers

Web developer