In this tutorial, you will learn how to improve the performance of components written in ReactJS applications. When building complex dynamic components using ReactJS, it can be very easy to bump into performance issues. I have been working with React forms recently. We had a requirement to customise it slightly. On the first attempt, when users started entering data into the form, the application would regularly blow up with a first maximum update depth exception. In this tutorial, I will cover some of the performance issues I had to figure out going through this process, so, hopefully, you can avoid the same pain as I did.
Limit The Props You Pass Into Your Components
A good practice software engineering 101 principle is to only pass the bare minimum amount of data into a function. The reason for this is threefold, it makes unit testing easier, it reduces the chance of bugs occurring, and finally, it makes your code easier to read and to maintain. This principle is even more important when it comes to React components. In a React component, when you make any changes to the state, React will automatically re-call render() to update the component. Applied sensible, this makes your webpage load a lot quicker. Applied foolishly, this optimisation can easily be abused and worsen performance. If it helps you to conceptualise this, you can think of a React component, like a state machine! If you pass lots of needed props into a component that change frequently, your application will be sluggish to use. Passing in the minimum props will prevent unneeded component rerenders.
Another performance issue that I encountered while using React forms, occurred when users entered data into a textbox. On each keystroke, the state of the form would get re-triggered. This caused the whole form component to re-render. In my situation, my component contained the complete form and all its form elements. A lot of data was passed into the component, for example, an array of countries where the user might live was passed in. Due to the re-renders, the component was calling an API on each re-render to get the list of countries. This meant the component was performing a lot of unneeded calculations on each user keystroke. The obvious way to reduce the number of rerenders was to prevent this country API from being called.
One way you might be able to apply this principle is to consider if you can move any logic out of your component and put it somewhere else, like within a parent component, or, maybe a redux reducer. My refactoring meant I moved the API call out of the component and into a store to prevent the API from being called so often.
Can Your Component Be Broken Into Smaller Components?
Preventing unneeded API calls was a good first step, unfortunately, this did fully resolve my performance issues. Another good practice to adopt when you're building React.js components is to write lots of small, well-defined components that do one thing well. Breaking components so they only have a single responsibility helps improve your code in several ways:
Smaller components are more declarative and easier to understand
Improve reuse. The larger you build your components, the less likely that they will be able to be used in other places. If you make a habit of writing small components and combine that with storybook you can get some good reuse from your codebase
The simpler the component, the easier it is to write a test for it.
Smaller components mean fewer props, which reduces re-renders!
My component contained the whole form and all its elements. When render()
was called React was re-rendering and re-calculating more than it needed to. If the user was filling in the first name of the form and a render()
was called by the validation, the whole form was being rendered. Splitting the form into a Form component, a CountryFormElement, TitleFormElement meant the main forms render()
method had less work to do. Splitting all the form elements into separate components, meant that when the user started typing in the first name component, only that element needed to be re-rendered, rather than the whole form.
Do Not Mutate Your State Data Within Your Render Method
Another React 101 - which is another functional programming recommendation - is to never mutate state. Aim to create pure functions. If you need to modify the value of some data that is passed into a component as a prop, clone it and update the clone rather than manipulate the prop directly.
If you are struggling to grasp why you should not modify your props state directly then take this example. Imagine you have an array being passed in as a prop and you modified it using array.splice()
. splice()
mutates the arrays state. This state change causes React to call render. If you have to modify an array, you are better off using something like map()
, filter()
or reduce()
. These methods do not change the state of the original array. For example, it would be more performant if you did this:
Also remember, if you want to change state it's better to deal with it in a more suitable place, like the mounted()
method.
Passing Functions
Be careful of functions that live within your render()
methods. A function can have unexpected side effects whenever the component state changes. It is common to see functions bound to the context of the component inside the render function to handle events of child components, like:
Both the approaches detailed in the code snippet above can create performance issues. Passing functions to children can cause the component to unnecessarily render multiple times during the lifetime of the application.
This brings us to the end of this guide. I hope my story has given you some tips on how to structure your components to avoid the rerender hell I experienced. Happy Coding 🤘