When I first started to learn React I was all for knowing all the little tips & tricks to increase performance. Even up until now, the main performance booster is to try and avoid the reconciliation process (where React performs comparisons in order to decide whether the DOM should be updated or not).

In this article I will try and list out a few easy ways to achieve better performance in your React app through simple development hacks. Under no means does this mean that you need to always apply these techniques, but it’s always good to know that they are there.

So, here we go:

1. Utilise render bail-out techniques

Each time a parent component updates, the children components update regardless of whether their props have changed or not. That means that even if the child component has exactly the same props as before, it will still re-render. To clarify, when I say re-render I don’t mean update the DOM, but pass through the process of reconciling whether there needs to be an update to the DOM or not. This is expensive, especially for big component trees, since essentially React will have to apply a diffing algorithm to check whether the previous & the newly computed tree have differences.

You can easily avoid that by either extending your class-based components from React.PureComponent , utilising the shouldComponentUpdate lifecycle hook or wrapping your components in a memo Higher-Order Component. This way you can make sure your component updates only when its own props change.

I should mention that by doing this for small components (like the ones in the example below), you will not only have zero actual benefit, but you might slow down your app by a tiny bit (since you are making React perform a shallow comparison on every render of the component). Thus, this technique should be used more aggressively for “heavy” components, but sparingly for components whose rendering time could be negligible.

TLDR; Use React.PureComponent , shouldComponentUpdate or memo() for “heavy” components, but avoid it for really small ones. If needed, break a large component to smaller subcomponents, in order to wrap the latter with memo() .

bail-out on rendering when possible

2. Avoid inline objects

Each time you inline an object, React re-creates a new reference to this object on every render. This causes components that receive this object to treat it as a referentially different one. Thus, a shallow equality on the props of this component will return false on every render cycle.

This is an indirect reference to the inline styles that a lot of people use. Inlining styles prop on a component will force your component to always render (unless you write a custom shouldComponentUpdate method) which could potentially lead to performance issues, depending on whether the component holds a lot of other subcomponents below it or not.

There is a nice trick to use if this prop has to have a different reference — because, for example, it’s being created inside a .map — , which is to spread its contents as props using the ES6 spread operator. Whenever the contents of an object are primitives (i.e. not functions, objects or Arrays) or non-primitives with “fixed” references, you can pass them as props instead of passing the object that contains them as a single prop. Doing that will allow your components to benefit from rendering bail-out techniques by referentially comparing their next and previous props.

TLDR; You can’t benefit from React.PureComponent or memo() if you inline your styles (or in general objects). In certain situations, you can spread the contents of an object and pass themselves as props to your component to combat that.

Avoid inline objects wherever you can

3. Avoid anonymous functions

While anonymous functions are a great way to pass a function prop (especially one that needs to be invoked with another prop as its parameter), they get a different reference on every render. This is similar to the inline objects described above. In order to maintain the same reference to a function that you pass as a prop to a React component, you can either declare it as a class method (if you are using a class-based component) or utilise the useCallback hook to help you keep the same reference (if you are using functional components). For times where you need a different reference for each set of parameters that your function gets invoked with (i.e. functions calculated inside a .map ), you can check out the plethora of memoize functions out there (like lodash’s memoize). This is called “function caching” or “listener caching” and helps you have fixed reference on a dynamic number of anonymous functions at the cost of browser memory.

Of course there are times where inline functions are the easiest way to go and don’t actually cause a performance issue to your app. This could either be because you utilise it on a very “lightweight” component or because a parent component actually has to re-render all of its contents every time its props change (thus you don’t care about the function getting a different reference since the component would re-render regardless of that).

One last important thing that I wanted to stress, is that — by default — render-props functions are anonymous.Whenever you utilise a function as the children of a component, you can define it outside of the actual component so that it always has a fixed reference.

TLDR; Try and bind function props to method or utilise useCallback as much as you can to benefit from rendering bail-out techniques. This applies to functions returned from render-props as well.

Keeping the same reference to a function across renders

4. Lazy load components that are not instantly needed

This might seem unrelated to the article, but the less components React mounts, the faster it can mount them. Thus, if your initial render feels rather junky, you can reduce the amount of clutter by loading components when needed, after the initial mounting has finished. Simultaneously, that will reduce your bundles and allow the users to load your platform/app quicker. Lastly, by splitting the initial rendering, you are splitting JS workloads into smaller tasks which will give your page breaths of responsiveness. This can easily be done using the new React.Lazy as well as the React.Suspense

TLDR; Try and lazy-load the components that are not actually visible (or needed) to the user until he interacts with them.

We are Lazy-loading the tooltip since it’s only visible when the user sees it. That will reduce the initial boot-up time for our app

5. Tweak CSS instead of forcing a component to mount & unmount

Rendering is costly, especially when the DOM needs to be altered. Whenever you have some sort of accordion or tab functionality — where you only see one item at a time — , you might be tempted to unmount the component that’s not visible and mount it back when it becomes visible.

If the component that gets mounted/unmounted is “heavy”, then this operation might be way more costly than needed and result into lagginess. In cases like these, you would be better off hiding it through CSS, while keeping the content to the DOM. I do realise that sometimes this is not possible since you might have a case where having those components simultaneously mounted might cause issues (i.e. components competing with endless pagination on the window), but you should opt to do it when that’s not the case.

As a bonus, tweaking the opacity to 0 has almost zero cost for the Browser (since it doesn’t cause a reflow) and should be preferred over visibility & display changes whenever possible.

TLDR; Instead of hiding through unmounting, sometimes it can be beneficial to hide through CSS while keeping your component mounted. This is a big gain for heavy components with significant mount/unmount timings.

Hiding through CSS instead of component unmounting

6. Memoize expensive calculations

There are times where a rendering is inevitable, but since your React component is a functional one, your rendering causes any calculations you have inside the component to be recomputed . The calculations whose values do not change on every render can be “memoized” using the new useMemo hook. Through that, you can bail-out of expensive calculations by using a value computed from a previous render. You can learn more about this here.

The overall goal is to reduce the amount of work JavaScript has to do during the rendering of a component, so that the main thread is blocked for less time.

TLDR; utilise useMemo for expensive calculation caching

useMemo can help optimize calculations

Conclusion

I intentionally left out stuff like “use the production build”, “use throttling on your keyboard listeners” or “utilise web workers” because i think this is kind of unrelated to React and more tied to general web development performance principles. What i tried to point out in this article are development practices that will help React perform better, free the main thread more and eventually make your app faster for the end user.

Thanks for reading :)

P.S. 👋 Hi, I’m Aggelos! If you liked this, consider following me on twitter and sharing the story with your developer friends 😀