Although the title seems clickbaity, in this article I’ll be talking about how you can use CSS to replace some of your JavaScript code in order to make your React apps more responsive. Before I even begin, I want to mention that the concepts laid out are mainly applicable to large & complex components and they will most likely yield no noticeable results in smaller components or apps. With that said, let’s begin:

Hiding instead of unmounting

Imagine that you have 2 or more tabs, all with loads of content. Whenever you are switching between tabs, the “React” way of implementing the tabbing behaviour would be to unmount the tab you “switched from” and mount the tab you “switched to”. Take for example Twitter’s profile tabs and try clicking on the “Followers” and “Following” tabs. The first time you do that it might take a while because Twitter is performing an HTTP request to fetch them, but if you keep alternating between the two, you’ll see that there is a delay between you clicking and the content appearing. Things get even worse if you attempt to go to the “Tweets” tab. This delay seems a bit weird since the content is already fetched, so what’s causing it?

The answer is the React runtime. Every time you mount a new component, React has to create the virtual DOM (a linked list of all subcomponents within the mounted component’s tree), run any code that you have within a component’s constructor (or the deprecated componentWillMount ) and then add the elements to the DOM. At the same time though, React has to also unmount the previous component, which means tearing down the virtual DOM, running the componentWillUnmount lifecycle methods and then removing the corresponding elements from the actual DOM.

It may come as a surprise, but unmounting a component is not cheap. You may think it’s just a deletion of stuff, but React needs to remove references from multiple places in the virtual DOM and run the lifecycle methods for every subcomponent that is contained within the soon-to-be unmounted component. That can be slow. Before React Fiber (up to React 15.x.x.) unmounting the previous component and mounting the next component was a sequential process. Nowadays, React fires those two in parallel in order to save time, since they are completely independent of one another.

The trick here is to not have React unmount or remount anything, but to utilise CSS in order to hide & show the content of these tabs. Just for fun, go to the same Twitter page and attempt to toggle the display of a tab by enabling and disabling the display: none rule through the browser’s devtools. You will notice that feedback is almost instant. The downside of this approach is that the DOM can get big if you have a loads of tabs with loads of content. Most of the times this is ok, since browsers handle hidden elements in a different way than visible ones, but you’ll have to test it on your app as well. In addition, you are not able to utilise React’s lifecycle hooks to perform side-effects, since you are not mounting & unmounting anything anymore. In order to combat that, you will have to pass a hideContents prop to the component (which will trigger the display: none ) and react to its changes using either useEffect or componentDidUpdate .

To wrap up this section, whenever you have heavy components that mount & unmount often, try hiding them instead. This will definitely yield better performance results, but it might require you to alter additional code in the components. Depending on your app, the responsiveness benefits may or may not overshadow the effort needed for these code alterations.

Animating in CSS

In the React world, it’s easy to use CSS to animate the “appearance” of a DOM element (i.e. the mounting of a React Component), but it can be hard to animate the “disappearance” of it (i.e. the unmounting of a component), since the node gets instantly removed and the CSS transitions & animations don’t have a chance to be applied. Usually people solve that by using animation libraries like react-spring , react-motion or react-css-transition-group . Most of these libraries, clone the DOM element and apply the fading-out styling to it, while the original element is already unmounted. The “fading-out” animation means that your React component will have to re-render multiple times, one for every “frame” of the animation. To combat this issue, libraries suggest you wrap the React component that you want to animate in an extra <div /> (often from the Animated library) in order to make sure you bypass the need for React to get involved in all this.

While techniques like these can suffice, they creates imperative animations which run on the same thread as your code; the main one. On the contrary, animations written in CSS are declarative and are handled by the browser’s compositor thread. Consequently, CSS animations are unaffected by the main thread’s more expensive tasks, thus the latter can respond to user input faster, making the whole app feel more responsive. The downside is that, unfortunately, JavaScript gives you more control over animations and it would still be the way to go for animations that have advanced effects, such as bouncing.

If you are using the first trick of the article (hiding instead of unmounting), animating the “disappearance” of an element suddenly becomes easy, since the DOM elements are still there and consequently CSS rules for animations & transitions can be applied. In general, CSS is ideal for simple transitions and should be preferred over JavaScript, especially when an app feels clunky during an animation. Of course, there may be lots of other things that can cause your app to feel clunky during an animation, but keep in mind that this is a contributing factor as well.

Removing CSS-in-JS runtimes

Most CSS-in-JS libraries allow you to write CSS rules using JavaScript. Libraries like emotion translate your JavaScript code into hashed CSS classes which are then applied to your components automatically. In order for them to be able to alter those styles when props change, they also include a runtime which helps them evaluate which class should be applied depending on the current props of the component. This does not only increase the overall footprint of your shipped code, but also slows it down (even by the slightest amount), because you have a runtime script that runs on the main thread each time the component’s props are modified. By not using a CSS-in-JS solution, you can reduce the overall JS code that gets shipped, while increasing your app’s responsiveness. The downside to that, of course, is a potentially worse dev experience since you’ll be losing the benefits that your existing CSS-in-JS solution has (theming, variables, etc.).

I want to stress that this is a very particular trick and 99% of the times you should not take into consideration at all. I’m just putting it out there as a thing to consider, since you might have a really heavy app that belongs in this 1% that would benefit from this.

Closing Notes

The aim of this article was to give you an insight on how to utilise CSS in order to solve common responsiveness issues in large-scale React apps. No solution to a problem is perfect, there’s always some sort of compromise. Perhaps after reading this, you may be able to compromise in a different way.

Thanks for reading :)

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