React Performance Demo

Leveraging React Hooks to improve rendering performance for large lists.

This project is a refresh of an older technical assessment I had taken in the past. With this, I had to create an editable list component where the user would enter values into a text field and these would be rendered in a list (as pills) with the ability to delete items.

While this worked according to spec, there was one underlying performance issue that only became noticeable after a large number of items were present in the list. When dealing with hundreds of list items, typing into the input became increasingly sluggish - not a good user experience.

Deeper dive into the problem

The major bottleneck was being caused by unnecessary renders of each of the pills in the component, which encapsulated an input field and a map function to render each pill. The React useReducer hook was used to maintain the state for the component. An action that updated the current input value was passed into the component, alongside a list of the current items to be rendered.

Whenever the props for the component were updated, all items in the list would be re-rendered. This happened even as the input text field was being typed in to - character by character. Any time a prop being passed into a component changes, the component re-renders itself (including all child components).

In the past, this could be solved by using the shouldComponentUpdate lifecycle callback in React class-based components. In here, an equality check could be made between key properties to check and see if a re-render is necessary. When using function comppnents, these lifecycle hooks cannot be used.

Solving the problem with memoisation

What is memoisation? Memoisation is an optimisation in computing where the results of a function call are cached and those cached results are returned without the need to recompute them again. This only changes when the inputs to the function call are different and the result may differ too.

In this particular use case, when delivering a large number of items to be rendered in a list, we are using function calls because we are using function components. Once an item in the list is rendered, it does not change, so it should not need to re-render. ReactJS has a built-in function called memo which is a higher order component that wraps around another component to ensure it only re-renders when inputs change.

When wrapping the component for a list item in the React.memo higher order component, I could still see that each list item was being re-rendered. Through the process of elimination, I removed all properties from the comppnent and introduced them back one by one, until I was able to identify which one was not being flagged as equal. It turns out that one of the properties which was a callback function was not being picked up. To solve this, I wrapped the delete callback function in a useCallback hook, which also uses memoisation.

This project source code and a live demo can be seen at https://matfin.github.io/editable-list-react/.