How to fix your React app when it secretly hates you

Ryan Crockett
Building CrowdRiff
9 min readOct 4, 2017

--

There comes a time in mostly every React app’s life-cycle where its developers learn they’ve unknowingly been developing in the Town With A Dark Secret. Everything seems fine at first, heck you just released your app, it feels great! Wow look at everyone and how happy they are to be using it. So many smiling faces! Then one day as you’re working on a new feature your browser — for a split second — flashes the words ‘unnecessary render methods’ on screen. “That’s odd” you think, but brush it aside because everything seems to still be working just fine.

Two days later this analogy ends with various mentions of further 👻 things happening and eventually you find yourself in a movie theatre watching yourself develop your app all over again while the world burns around you — the realization that all along there were some troubling performance issues tucked away in your code base slowly dawning on you.

Pretty recently at CrowdRiff we released a fancy shiny C O O L completely new version of our platform built in React (the previous version was built in Angular) — a process that took a little over a year, and involved several front end developers learning a lot about React along the way. Now that we’ve crossed the finish line and clients are using the new app, we’ve started going back into some of the old “hey i'm a baby what is react?” code and LET ME TELL you it’s sometimes a big ol’ mess.

The mess.

Step 💡: React? A preface

Before getting into the meat of how to fix performance problems in React, I think it's super important to first understand what situations ~in general~ cause React to perform badly: React works thanks to something awesome called “The Virtual DOM” (or 👻 for short) being compared to something called “The DOM” (or 🙂 for short). Every time any change in the state of your app occurs, those changes will happen in 👻 first. 👻 will update its ghostly components, and then React’s diffing algorithm will compare those changes against 🙂. Should any irregularities occur (which they will in this case), the differ will be all like “hey 🙂 sorry to interrupt but you’re out of date” and 🙂 will be all like “oh pardon me! I shall update” and it will.

This is G R E A T. But sometimes its not so G R E A T. React’s diffing algorithm is really really smart, but sometimes its not a great communicator. When it tells 🙂 that it needs to re-render a component, it's not really concerned about that re-render will actually do anything useful to the client — only that it happened in 👻 and therefore should happen in 🙂. This is what performance problems mostly stem from.

Step 1: Show 👏 the 👏 receipts

Like with most problems, the first most hardest step into solving this one was admitting that it existed. The new CrowdRiff platform already felt fast, why should we be overly concerned about making it even faster? Especially when there are so many new important super-fun-to-work-on features in the pipeline!

The problem with React in this instance is that in most cases if one component is performing badly, it will ruthlessly drag down the performance of its siblings, its children, and sometimes even its poor never-meant-any-harm parents. Because React component development is formulaic by nature chances are good that you’re not just going to have one “bad” component; but a philosophy that encourages this kind of “badness” instead. But how do you get your fellow developers to recognize that changing this philosophy is important?

How do you convince this person?

With tools! There are a ton of awesome tools built by the React team and its community to identify this very problem. I have found the following to be the most enlightening:

React Perf is a great tool not only in identifying poorly performance components, but also in showing that the work that you put into improving those components actually makes a difference. Rambling on about reduced render methods and shorter compute times with your sales and marketing team probably isn’t going to go anywhere, so it’s nice that Perf gives you a tidy little chart with which you can easily compare before and after.

Perf is super great at the “what” in the sentence “hey what is making my app so slow and why is it like this?” but not so great at everything after “why”. In comes “why-did-you-update”; a convenient little library that will shout out into your console any time a prop or piece of state makes your component update when it really shouldn't have. Using this in tandem with Perf will allow you to identify problem components, and gain awareness of each and every time they slow your app down.

Step 2: Keep State Comparable And Flat 🔨

Tools in hand, problem philosophy identified, now its time to start fixing things. Based on step 💡 we know that a React component re-renders when one of its props does not what it used to, with frustratingly little regard for whether or not that re-render will actually change anything the client cares about. But thats okay, we still love React and there are tons of ways to get around this cute little quirk.

I’m going to assume you’re already doing awesome things like not mutating state, and using whatever your fav state management library is (should your app be big enough to require one…if not then definitely do both), so let's talk about shouldComponentUpdate , its cool older brother PureComponent , and why its important to keep your state comparable.

Both give you a way to prevent your component from rendering by comparing its current state to its future state. For example, lets say you have a component that looks like this:

Our render method only really cares about coolness when it was less or greater than 10, and then switches to the opposite. In between, render() should not be called, but it will be because React’s diffing algorithm isn’t aware of the fact that we would prefer if it didn’t. shouldComponentUpdate gives the component the ability to intercept this change request and decide for itself whether or not it should call its render method based on the props, and state that are passed into it. With shouldComponentUpdate CoolComponent could look like this:

Now the render method will only be called if:

  • coolness was greater or equal to ten, but will be less than 10 after updating
  • coolness was less than 10, but will be 10 or greater after updating

As your compent expands in complexity you can add further checks to shouldComponentUpdate — as long as it either returns true or false it can be as complex as you need it to be.

But, you shouldn’t want it to be! Running a set of complex checks everytime your component thinks it might want to update has a lot of potential to become more of a performance hindrance than enhancement. Thankfully, there’s a better way!

PureComponent's magic lies in the following bit of code:

if (this._compositeType === CompositeTypes.PureClass) {
shouldUpdate = !shallowEqual(prevProps, nextProps) ||
! shallowEqual(inst.state, nextState);
}

If react detects that your component is PURE (and good and nice) it will add a shouldComponentUpdate method to it that will shallowly compare the current props to the next props, and the current state to the next state. If they’re equal it wont update, if they’re not it will.

Because this comparison is shallow it now becomes important to keep your state and props as shallowly comparable as possible. Instead of passing down huge objects to your components, pass down only the pieces of state it will actually be using from that object.

Say for example we have a component that looks like this:

CoolPic will have its render method called in the following situations:

  • If any of the state its care about changes ✅
  • If the coolpic object has any other values, and they change ⚠️
  • If the coolpic is part of a parent object like say, users , and that object changes ⚠️
  • If CoolPic's parent component re-renders ⚠️
  • If anyone so much as sneezes at coolpic 💩

This is not good! To prevent this we can flatten out coolpic and only pass down the parts of it that the CoolPic component actually cares about. Which would look something like this:

Now CoolPic will only have its render method called in the following situations:

  • If any of the state it cares about changes ✅

Whoa thats like, 4 less bullet points — crazy!

For redux users, this functionality is mostly provided out of the box. Redux’s connect function will run a similar shallow comparison to the current state you’re connecting to, and the changes that are planned for it. If it detects no significant changes it wont update your component. So, if you this same “keep everything flat always” mentality to your connected components you’ll see a similarly significant increase in performance.

Last Step I Promise: Split Everything Up

When I first learned React (and redux) the convention at the time was having a single massive “container” component to connect to and handle all of your apps state for that feature or route —passing it down to any child components that might need it. 🙈 This is not a good idea 🙅🏼.

As we’ve learned if one component is connected to heaps of state, there are heaps of chances for it to re-render for no reason. If a component’s parent is re-rendering all over the town when it shouldn’t be, it will follow suit and also re-render because it has no idea that what its parent is doing is wrong, it doesn't know any better, it's just a kid!

To solve this problem you have to introduce some component independance into your app. Instead of having large state heavy components manage the state for their child components, give the child components the ability to manage their own state.

Here’s an example of a bad parent:

In this example, the BadParent component doesn’t actually use any of the props its connecting to. Even worse, it’s child components don’t all make use of all three of the props their parent is connected to. If hugeArray were to every change and cause a re-render it would also cause Child1 and Child2 to re-render even though they don’t care about hugeArray and all its dumb values.

To fix this, lets just connect our kids:

Now our component has no idea whats going on with its kids — and thats a good thing. Each child component is in charge of only the state it cares about, and will therefore only re-render if that specific state changes.

Not only does this cut down on the amount of wasted renders, but it also opens up the opportunity to introduce fancy code splitting things like react-loadable more easily which is also a good thing.

To Summarize

  1. Only give a component the props it actually needs to render.
  2. Keep component state as flat and as comparable as possible; strings, and booleans are like the coolest.
  3. Make heavy use of PureComponent and shouldComponentUpdate where applicable (and connect if you’re using react-redux).
  4. If you havent yet talked about performance with your dev team, talk about it.
  5. If your dev team thinks you’re some sort of raving performance lunatic and should be sat at the corner desk where the light is like really hot and the desk is kinda too short then use React’s built in “perf” tool and why-did-you-update to show them how wrong they are.
  6. Always have fun.

--

--