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.
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?
Infleux is a platform to connect brands and influencers!
The tools we used are:
create-react-app: Start a React.js app with webpack and babel preconfigured.
redux: State management lib that can be used with React.js
redux-saga: Library for side-effects (e.g.: async actions) with
After some iterations of both the business and the application, we finally had a stable version, and some problems started to show up:
- Performance issues: the platform felt “sluggish” overall, scroll was slow, a simple toggleable menu took way longer than needed to open/close.
- Project file structure: We started with the Rails-style folder structure, i.e., separated folders for
sagas, but found some issues with this.
- Bundle size: Our SPA bundle, the file sent to the client, was only getting bigger and bigger with each feature added.
I’ll 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”
- Show menu is a button that toggles the Todo Filters.
- Changed the
todos.jsto use filter method.
Here is the CodeSandbox.
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…
Why is this happening?
This part of the code is the culprit:
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() 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)
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 (
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.
Using an arrow function in render creates a new function each time the component renders, which may have performance implications (see below).
PureComponentperforms 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()is invoked before rendering when new props or state are being received. Defaults to
i.e., when you use
Component your components re-renders every
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.
Non react issues
We also had some issues unrelated to
React that affected performance:
border-radiuson iPhone was REALLY slowing down the scroll for lists. We just removed it from mobile, but what was happening still intrigues me.
- 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.
Shout out to the community
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: