6 React Anti-Patterns to Avoid

Avoid these common mistakes to build a high performance app

Frameworks and Languages

React.js is a minimal and elegant library. Just create a javascript function to represent a component that instantly renders, when your data changes. It is simple, but also misleading. In reality, React is a highly complex UI library, with a great quantity of technical baggage that has accumulated in recent years.

Numerous modern frameworks try to do things more efficiently, but React is still the king and if you wish to be a web developer in 2022, it's a very good skill to cultivate. The problem is that React is unopinionated yet provides various ways to solve the same problems leaving developers with lots of room to screw things up with their own bad ideas.

In today's post, we'll look at the most common anti-patterns in React in addition to tricks and tips to enhance our codebase.

1) Nested Components


It can be called the scoped components. When writing components in some cases we might be lured to nest them like this:

import { useState } from 'react';

export const ParentComponent = () => {
    const [count, setCount] = useState(0);

    const ChildComponent = () => (
      <div>Hello World</div>
    );

    return (
     <div>
        <ChildComponent />
        <button onClick={() => setCount(count + 1)}>Count me</button>
     </div>
    );
};

It makes sense after all since the child component is not going to be used outside of MainComponent and it can currently use the name state variable without having to receive it as a prop. However, this nesting of functions actually creates a small performance issue.

You see, every time your parent component is called, it'll re-create the child component's definition. It'll keep on declaring the same function over and over again on every execution. If you're coding this everywhere, it might be a small issue that you may not even notice impacting your performance, that you may not even notice

Better to create child components separately.

import { useState } from 'react';

const ChildComponent = () => (
   <div>Hello World</div>
);

export const ParentComponent = () => {
    const [count, setCount] = useState(0);

    return (
     <div>
        <ChildComponent />
        <button onClick={() => setCount(count + 1)}>Count me</button>
     </div>
    );
};

2) Props Drilling


Now another problem you may face with big projects is that you may have one component at the very top that holds your state but then you have a deeply nested component that likewise needs to use that state. What you need to do is then pass that state as a prop to a lot of intermediate components that do not actually require it until it finally gets to that child.

Props drilling just like making the holes from top to deepest components

This is known as prop drilling because it makes you wish to drill a hole into your brain. One solution is to bring in a state management library like Redux, However, that's a heavy-handed approach. When you have global data that is used everywhere, like an authenticated user, for example, you can share it everywhere with the context API. You initially provide the data at a certain point in the component tree rather than in any component nested under it at any level. Enabling access to that data sounds awesome, however, it does come at an expense the context API is something that you should use sparingly because it makes your components impossible to reuse without having provided as a parent.

The majority of apps only have a handful of values that are truly global. That stuff enters into context while everything else must be more localized.

3) Use Context for Global Scope


In some circumstances, the line between state and context might end up being blurry. However, you would do well to keep in mind that they are two very different things, meant for different use cases.

Context allows you to share props between related components, helping you prevent the dreadful "prop drill down" effect, where you need to pass the same prop through 4 different levels just to get to the actual component that needs it.

The global state, on the other hand, is meant to manage values that prevail in the entire application.

If you wish to use context in this circumstance, you'd need to develop a context provider to wrap your entire application in, which would go against the entire purpose of said constructs.

Context isn't a magic wand, don't use it for entire application


Similar to all other examples in this list, you can perfectly use context to handle global state management. Nevertheless, once you start doing it on an ever-growing application, things will get out of hand.

I recommend using Redux to store the global state and use Context API for specific parts of your application within a well-defined bounded context.

4) Huge Components Tree In The Single Component


When I first start building a React app I normally begin by creating one big giant component, typically in the app component generated by CLI like create-react-app and that's deliberate, because I normally do not know how I desire my code to be arranged initially, and I do not want to squander my time over organizing things before I even understand what I wish to do.

When you have so much branches in your component tree...
However, this leads to an anti-pattern of having one overly large, deeply-nested component. With a component like that, It's hard to understand refactoring and testing. We can improve that code by refactoring it into reusable components that better represent what it does. Now that may seem like a lot of work, but luckily there is a VSCode extension called Glean that can take advantage of the IDE to do this work for us. It's a huge time saver that I wouldn’t want to live without.

5) Using Index as a key in the loop


We've all been there, needing to render a list of things and rapidly count on the useful syntax shown below:

<ul>
    {items.map((item, index) => <li key={index}>{item.name}</li>)}
</ul>

The example is simple, but notice how we're using the index variable provided by the map method. We need to provide a key to our elements, so we might as well use this one, right?

No! Don't use it, please...

Because you're hardly ever rendering such a simple list, mainly. In fact, you're generally rendering a list of things that may change, be re-ordered, or are more complex than just a string. And what happens then? The value of the key is used by React to comprehend when an element is added, changed, or removed. It is meant to act as a unique identifier of the element and not a simple index within the list.

While you might think that in our example the index used actually acts as an ID, consider what would occur if we were to insert more elements into the middle of the list of users. Instead, the new index values would reference different users therefore a different component (a different li element). React would get confused and produce unexpected behaviors. 

Take your time to produce a unique ID for all your elements, it'll save you a lot of headaches. Then after you have the unique ID you can rewrite the code like this:

<ul>
    {items.map(item => <li key={item.ID}>{item.name}</li>)}
</ul>

6) Don't Memorize a Complex Calculation


Here's a similar example of a component that does too much work and may end up causing performance issues. Let's think of a component with two different state. On one state, we need to run an expensive calculation anytime the value changes.

import { useState } from 'react';

export const ParentComponent = () => {
    const [count, setCount] = useState(0);

    const bigData = reallyHeavyCalculation();

    return (
     <div>
        <button onClick={() => setCount(count + 1)}>Count me</button>
     </div>
    );
};

The problem with our current execution is that anytime, the state changes on this component, it will rerun the expensive calculation even though it just depends on the one count value it may not be very obvious here, but you have the possible performance bottleneck. In a situation like this, you'll want to use the useMemo hook. What it will do is remember the last value and just run the actual function when its dependent data changes.

import { useState, useMemo } from 'react';

export const ParentComponent = () => {
    const [count, setCount] = useState(0);

    const bigData = useMemo(() => reallyHeavyCalculation(), []);

    return (
     <div>
        <button onClick={() => setCount(count + 1)}>Count me</button>
     </div>
    );
};

Likewise, you might encounter the circumstance when dealing with functions and for that, React provides the useCallback hook as well.

Conclusion


Although React might allow us to solve the problem in the various ways, avoid these anti-patterns so that you can do a high-performance apps all the time.
Sometimes,  these anti-patterns may start like a snowball, accumulating into an avalanche of problems later.

Looking for a new challenge? Join Our Team


Like 7 likes
Noom.T Teja
I'm a fullstack javascript developer who love talking about architecture, system design, framework and libraries, developer mindset. I also interesting in business and investing as well.
Share:

Join the conversation

This will be shown public
All comments are moderated

Comments

Michu
December 16th, 2022
Point #1 uses wrong reasoning against it. Creating a function is close to zero cost. Notice that with hooks you create new functions all the time anyway. Even though hooks like useEffect know when to call your function, that function is still being created on every render! (just ignored) The real reason why #1 is an anti-pattern is that you are recreating an element from scratch on every render. Like you said, not a huge deal, but can be a problem if there are thousands of those.

Get our stories delivered

From us to your inbox weekly.