We can consider React the king of the web when it comes to frontend development. It’s a lightweight JavaScript library whose functionality and flexibility won the hearts of coders all around the globe. Blazing fast web applications, reusable components that help to save time on development, and Virtual DOM allowing to update separate parts of the app as needed are only a few examples of killer features that determine React’s success. As a result, in 2021, it has become the most used web framework, according to Statista:
However, such popularity of React doesn’t save programmers that use it in their projects from poor development practices. React enables developers to create components and reuse them again in their code or even import them into other projects. Without proper attention to code quality, bad programming habits risk to decrease system maintainability and turn React advantages into dust. Today, we’ll consider some examples of React anti-patterns which avoidance will help you ensure the top-notch quality of your web applications.
What Dangers Anti-patterns Hold and How to Detect Them Timely
Programming languages and web development frameworks are tricky. It may seem that they do what you want them to do, but the question is how to ensure that you use them the right way. You can import the libraries you need, create the components you want, and render everything you desire on the screen, which doesn’t necessarily mean that there’s no room for improvement. Moreover, it doesn’t mean that your project won’t fall apart if you decide to reuse some of your components somewhere else.
If you create a piece of code or a component that you or any other programmer can reuse later with no effort, it’s a sign of a good pattern. If the code is easy to review, maintain, import, and debug, the chances that you use a good pattern are even higher. We can consider everything that works in the opposite way an anti-pattern. Even experienced developers may become victims of anti-patterns if they won’t pay due attention.
Read Also The Price of Popularity. Strong and Weak Sides of ReactJS and React Native
Fortunately, there are some signs that help to detect anti-patterns in the React code. For example, when you build a web application using this library, you want different parts of it to be interconnected. It helps to ensure that the whole app has the desired condition when all components are dependent on each other. When you break this rule by using, for example, the useRef hook that doesn’t take dependency arrays, you increase the probability of potential issues, so it’s better to be careful in such scenarios.
Another example is excessive nesting. For example, it may seem pretty normal to create a parent component that has a child component if the specifics of the application layout require it. The child can be a parent to another component, and so on. This rabbit's hole can go pretty deep, depending on the complexity of your React application. The problem is, when there’s a bug in, say, child component number 10, you’ll have to go through the entire tree of its parents to find the source of it.
Examples of React Anti-patterns You Better Avoid
When you use too many nested components, your code can become a real headache. Let’s inspect an example we’ve already mentioned since it appears pretty often among inexperienced developers trying to enter the world of React JS development. For example, you may nest your React components like this:
import { useState } from 'react';
export const ImAParent = () => {
const [count, setCount] = useState(0);
const ImAChild = () => (
<div>This is a child component</div>
);
return (
<div>
<ImAChild />
<button onClick={() => setCount(count + 1)}>Counting</button>
</div>
);
};
There’s nothing to worry about here, you may say. After all, there’s no other place to use the child component rather than within the parent component. Unfortunately, there are some hidden drawbacks to such an approach. You risk facing some performance issues in the first place. We’ve used a pretty simple example, but in real life your React code may comprise dozens of nested components. Each time there’s the need to re-render the app, the parent component will have to execute the code related to the child component. Even if there’s no new data for the child component to display, the parent will execute the declaration function repeatedly. The more nested components your code has, the more computing resources will be spent on this meaningless task. Imagine how much damage it can cause if you decide to import this code in other projects. Therefore, it’s important not to declare components inside their parent components. As an alternative, you can use something like this:
import { useState } from 'react';
const ImAChild = () => (
<div>This is a child component</div>
);
export const ImAParent = () => {
const [count, setCount] = useState(0);
return (
<div>
<ImAChild />
<button onClick={() => setCount(count + 1)}>Counting</button>
</div>
);
};
Underestimating the results of complex calculations is another habit that you better avoid. Let’s imagine that you want to build an app that works with big data and relies on heavy calculations. In this scenario, you might decide to use React code that looks like this:
import { useState } from 'react';
export const BigDataAppComponent = () => {
const [count, setCount] = useState(0);
const bigDataStuff = someComplexCalculations();
return (
<div>
<button onClick={() => setCount(count + 1)}>Counting</button>
</div>
);
};
Believe us, this component performs some really heavy calculations. The problem is that this perfectly looking code may cause some serious performance issues. If there’s an action that changes the state of the component, it’ll have to perform all these calculations again even if there’s no new data. If you reuse this code in different parts of your React app, you may face some serious problems. Luckily, you can always import and use the useMemo hook that can remember the results of previous calculations and save you from the waste of computing power if there are no data changes:
import { useState, useMemo } from 'react';
export const BigDataAppComponent = () => {
const [count, setCount] = useState(0);
const bigDataStuff = useMemo(() => someComplexCalculations(), []);
return (
<div>
<button onClick={() => setCount(count + 1)}>Counting</button>
</div>
);
};
Read Also React Native for Mobile Development. Killing Two Birds With One Stone
State management is one of the most challenging tasks regarding React applications that affects maintainability and scalability. To avoid unnecessary complexity, it may be useful to limit your intention to store variables in the state as much as possible. To do so, you can follow a concept of derived state. It implies that you must prefer the use of variables that you can compute on-the-fly. For example, if you have a large form that contains tons of checkboxes, you can determine whether some of them are checked or not by going through the items array and filtering them each time there’s the need to re-render the component. Following the derived state patch is a sure way to keep data that your app works with in sync when new changes are made.
Conclusions
React is a pretty powerful development tool. Like any other tool, it can be used for building beautiful things, but in the wrong hands, it can introduce flaws in the project. Even if everything seems to work just fine, some code parts may consist of seemingly harmless anti-patterns resulting in reduced performance and potential maintenance problems. The good news is that experienced programmers are well aware of them, so you have nothing to worry about if you decide to cooperate with a reliable software development team.