Real world React learnings, part one

Photo by Lucas Brito

Performance ain’t a problem. Until it is.

This is the first post of a three part series explaining some lessons learned running React in production.

Intro

When working on a MVP for Infleux, the team decided to move fast and optimize later. We knew the application would eventually hit some non functional requirements problems, but if the business model isn’t even valid, what’s the point of optimizing early and overengineering?

Related XKCD

Infleux is a platform to connect brands and influencers!

Screenshot of the dashboard

The tools we used are:

  1. create-react-app: Start a React.js app with webpack and babel preconfigured.
  2. redux: State management lib that can be used with React.js
  3. redux-saga: Library for side-effects (e.g.: async actions) with redux.

Problems

After some iterations of both the business and the application, we finally had a stable version, and some problems started to show up:

  1. Performance issues: the platform felt “sluggish” overall, scroll was slow, a simple toggleable menu took way longer than needed to open/close.
  2. Project file structure: We started with the Rails-style folder structure, i.e., separated folders for actions, constants, containers, reducers, components and sagas, but found some issues with this.
  3. Bundle size: Our SPA bundle, the file sent to the client, was only getting bigger and bigger with each feature added.

Ill write a post for each topic — detailing everything in a single post would make it too long and the idea is to keep it short.

Dealing with performance issues

To analyze this, I used mainly Chrome DevTools and React Developer Tools. They’re really powerful tools and of great help in debugging.

One really useful feature of this tool is that it enables you to highlight which components are updated. To turn it on, open Chrome DevTools, go to the React tab, and click on the checkbox “Highlight Updates

Chrome DevTools screenshot

To reproduce the issue we were facing, I’ve forked a CodeSandbox (original) that implements the Todo List example from redux docs and made some changes to it:

  1. Show menu is a button that toggles the Todo Filters.
  2. Changed the getVisibleTodos function on todos.js to use filter method.

Here is the CodeSandbox.

The blue rectangles are highlighting the updated components.

Whenever the button for opening/closing the menu is clicked, the todo list is also updated, even though an unrelated value was changed! Well, our application had a really long list (lots of DOM nodes) that was constantly re-rendering on every interaction making everything slower. So…

This part of the code is the culprit:

This is the butler.

What happens is every time the mapStateToProps function is called, the function getVisibleTodos returns a new array and this makes TodoList re-render because:

To change this behaviour, you’d have to either change mapStateToProps or the component’s shouldComponentUpdate().

Changing the shouldComponentUpdate() would depend on the data and component being optimized, and that could mean scalability problems (should we optimize every new React component according to the screen? what about reusability?), so we put this solution on hold.

The other option, to improve mapStateToProps is actually the recommended approach. I’ve modified the previous sandbox to use reselect and we can see that the list doesn’t update anymore when the menu button is clicked! (Link)

New sandbox using reselect

What does reselect do? It is a memoized selector, meaning it caches the result given the inputs and return the same value/reference, meaning the strict equality (===) returns true.

Disclaimer: This is the example from reselect’s motivation.

Yes, inline arrow functions can be problematic. An arrow function has the same problem as computing derived data: a new function is passed in every render. This may cause unnecessary re-renders.

From React official documentation:

Using an arrow function in render creates a new function each time the component renders, which may have performance implications (see below).

PureComponent performs a shallow comparison of props and state, and reduces the chance that you’ll skip a necessary update. (From React Docs)

PureComponent implements the shouldComponentUpdate lifecycle.

shouldComponentUpdate() is invoked before rendering when new props or state are being received. Defaults to true.

i.e., when you use Component your components re-renders every props or state change, regardless of the change.

Also, another possible solution that could have been applied in our case (which we didn’t for reasons particular to our design) was using react-virtualized, what it does is rendering only what’s visible on the screen.

This is really useful when you have large lists of images and you don’t want the user to load all of them before they are actually on screen.

We also had some issues unrelated to React that affected performance:

  1. using border-radius on iPhone was REALLY slowing down the scroll for lists. We just removed it from mobile, but what was happening still intrigues me.
  2. tachyons’s hover style has transform: translateZ(0); to force GPU rendering and this was slowing scroll for mobile users.

As you may have noticed, some of our problems are actually caveats on most official documentation, but as they say:

Some lessons are better learned the hard way.

One of the really cool things about working with OSS is that other people probably had the same problems you’re facing, and you’re never really alone when debugging something, so I’d like to link some posts/articles that helped me when debugging and learning about React.js performance:

This story is published in Noteworthy, where thousands come every day to learn about the people & ideas shaping the products we love.

Follow our publication to see more product & design stories featured by the Journal team.

Full time dev @ Infleux / RevMob. BJJ & Yoga enthusiast. 🇧🇷