At this point, it’s tough to argue that React is one of the most loved libraries on the planet. There is a tremendous amount of interest in React and new developers are swayed into the platform because of its UI-first approach. And while both the library and the entire React ecosystem have matured over the years, there are certain instances where you find yourself asking “what’s the right way to do this, exactly?”
And that’s a fair question to ask — there isn’t always a firm “right” way of doing things. In fact, as you likely already know, sometimes best practices aren’t so great. Some of them can compromise performance, readability and make things unproductive in the long run.
In this article, I’ll describe 5 generally accepted development practices that you can actually avoid when using React. Naturally, I’ll explain why I consider the practice avoidable and suggest alternative approaches that let you accomplish the same thing.
Optimizing React right from the start
The developers at React have put a lot of effort into making React fast and new optimizations are added to the mix after each new update. In my opinion, you shouldn’t spend time optimizing stuff until you see actual performance hits.
It’s easier to scale React compared to other front-end platforms because you don’t have to rewrite entire modules to make things faster. The usual culprit that causes performance issues is the reconciliation process that React uses to update the virtual DOM.
Let’s have a look at how React handles things under the hood. On each render(), React generates a tree that’s composed of UI elements — the leaf nodes being the actual DOM elements. When the state or props get updated, React needs to generate a new tree with minimal number changes and keep things predictable.
Imagine you have a tree that looks like this:
Imagine that the application receives new data and the following nodes need to be updated:
React usually ends up re-rendering the entire subtree instead of rendering only the relevant nodes like this:
When the state changes at top-order components, all the components below it get re-rendered. That’s the default behavior and it’s okay for a small-sized application. As the application grows, you should consider measuring the actual performance using Chrome Profiling Tools. The tool will give you precise details about the time wasted on unwanted renders. If the numbers are significant, you can then optimize the rendering time by adding a shouldComponentUpdate hook into your component.
The hook gets triggered before the re-rendering process starts and by default, it returns true:
When it returns true, React’s diff algorithm takes over and re-renders the entire subtree. You can avoid that by adding comparison logic into shouldComponentUpdate and updating the logic only when the relevant props have changed.
The component won’t update if any other props/state has changed except color/count.
Apart from this, there are certain non-React optimization tricks that developers usually miss, but they have an impact on the application’s performance.
I’ve listed some of the avoidable habits and the solutions below:
- Unoptimized images — If you’re building on dynamic images, you need to consider your options while dealing with images. Images with huge file sizes can give the user an impression that the application is slow. Compress the images before you push them into the server or use a dynamic image manipulation solution instead. I personally like Cloudinary to optimize react images because it has its own react library, but you could also use Amazon S3 or Firebase instead.
- Uncompressed build files — Gzipping build files (bundle.js) can reduce the file size by a good amount. You will need to make modifications to the webserver configuration. Webpack has a gzip compression plugin known as compression-webpack-plugin. You can use this technique to generate bundle.js.gz during build time.
Server-side rendering for SEO
Although Single Page applications are awesome, there are two issues that are still attributed back to them.
Here’s an excerpt from the Webmaster blog back in October 2015.
If you’re only using server-side rendering because you’re worried about your Google Page Rank, then you don’t need to use SSR. It used to be a thing in the past, but not anymore.
However, if you’re doing it to improve the initial render speed, then you should try an easier implementation of SSR using a library like Next.js. Next saves you time that you would otherwise spend on setting up the Node/Express server.
Inline styles & CSS imports
While working with React, I’ve personally tried different styling ideas to find new ways to introduce styles into React components. The traditional CSS-in-CSS approach that has been around for decades works with React components. All your stylesheets would go into a stylesheets directory and you can then import the required CSS into your component.
However, when you’re working with components, stylesheets don’t make sense anymore. While React encourages you to think of your application in terms of components, stylesheets force you to think of it at the document level.
Various other approaches are being practiced to merge the CSS and the JS code into a single file. The Inline Style is probably the most popular among them.
You don’t have to import CSS anymore, but you’re sacrificing readability and maintainability. Apart from that, Inline Styles don’t support media queries, pseudo classes and pseudo elements and style fallbacks. Sure, there are hacks that let you do some of them, but it’s just not that convenient.
That’s where CSS-in-JSS comes in handy and Inline Styles are not exactly CSS-in-JSS. The code below demonstrates the concept using styled-components.
What the browser sees is something like this:
A new <style> tag is added to the top of the DOM and unlike inline styles, actual CSS styles are generated here. So, anything that works in CSS works in styled components too. Furthermore, this technique enhances CSS, improves readability and fits into the component architecture. With the styled-components lib, you also get SASS support that’s been bundled into the lib.
Nested ternary operator
Ternary operators are popular in React. It’s my go-to operator for creating conditional statements and it works great inside the render() method. For instance, they help you to render elements inline an in the example below, I’ve used it to display the login status.
However, when you nest the ternary operators over and over, they can become ugly and hard to read.
As you can see, the shorthand notations are more compact, but they make the code appear messy. Now image if you had a dozen or more nested ternaries in your structure. And it happens a lot often than you think. Once you start with the conditional operators, it’s easy to keep on nesting it and finally, you reach a point where you decide that you need a better technique to handle conditional rendering.
But the good thing is that you have many alternatives that you can choose from. You can use a babel plugin like JSX Control Statements that extends JSX to include components for conditional statements and loops.
There’s another popular technique called iify ( IIFE — Immediately-invoked function expressions). It’s an anonymous function that is invoked immediately after they are defined.
Here is an example that demonstrates how we’re going to use IFFE in React.
IIFE’s can have an impact on performance, but it won’t be anything significant in most cases. There are more methods to run conditional statements in React and we’ve covered that in 8 methods for conditional rendering in React.
Closures in React
But when you are using closures inside the render() method, it’s actually bad. Every time the SayHi component is rendered, a new anonymous function is created and passed to Button component. Although the props haven’t changed, <Button /> will be forced to re-render. As previously mentioned, wasted renders can have a direct impact on performance.
Instead, replace the closures with a class method. Class methods are more readable and easy to debug.
When a platform grows, new patterns emerge each day. Some patterns help you improve your overall workflow whereas a few others have significant side effects. When the side effects impact your application’s performance or compromise readability, it’s probably a better idea to look for alternatives. In this post, I’ve covered some of the practices in React that you can avoid because of their shortcomings.
What are your thoughts about React and the best practices in React? Share them in the comments.
Plug: LogRocket, a DVR for web apps
LogRocket is a frontend logging tool that lets you replay problems as if they happened in your own browser. Instead of guessing why errors happen, or asking users for screenshots and log dumps, LogRocket lets you replay the session to quickly understand what went wrong. It works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store.
Try it for free.