3 React Anti-Patterns and How to Fix Them

React is a powerful JavaScript library for building user interfaces, but it is important to avoid certain common mistakes that can hinder the performance, maintainability, and scalability of your application. Here are three React anti-patterns that you should be aware of:

Too Many Divs

Picture this: we’re in the zone defining a new React component that returns two child elements. Maybe our code looks a little like the block below:

    const App = () => {
    return (
        <h1>Hello World!</h1>
        <p>Welcome to my little corner of the internet.</p>
      );
    };

Everything is going swimmingly. All seems fine until we get an error 😱:

React JSX Error

Our first instinct may be to nest those two elements in a div and call it a day. Now our code looks something like this:

    const App = () => {
    return (
      <div>
        <h1>Hello World!</h1>
        <p>Welcome to my little corner of the internet.</p>
      </div>
      );

There isn’t necessarily anything wrong with the code above, but it will lead to useless div elements in our markup as our application grows. We’ll run into problems adding accessibility features down the line and CSS styling is going to become an even bigger pain than it already is! Luckily, React gives us a nice way of handling that pesky JSX error. Behold, the React fragment 🙌😩:

    const App = () => {
      return (
        <React.Fragment>
          <h1>Hello World!</h1>
          <p>Welcome to my little corner of the internet.</p>
        </React.Fragment>
      );
    };

<React.Fragment> is a built-in React component that will allow us to return a group of child elements without a parent. This eliminates the potential for unnecessary elements in the DOM once our code compiles. We might also see the short-hand syntax used in the wild, which does the same thing as a <React.Fragment> :

    const App = () => {
      return (
        <>
          <h1>Hello World!</h1>
          <p>Welcome to my little corner of the internet.</p>
        </>
      );
    };

Relying On Array Index Positions

We’ve all been there. We’re in a situation where we need to render a list of elements from a for loop or map function and we might rely on the really convenient syntax below:

    const users = [{ name: 'Pilar' }, { name: 'Helmut' }];
    const App = () => {
      return (
        <ul>
          {users.map((item, index) => (
            <li key={idx}>{[user.name](http://user.name/)}</li> 
          ))}
        </ul>
      );
    };

Everything looks to be in order in the example above 🤔, right? We’ve got an array called users that has two objects each with a key called name . We’re using an index variable inside our map function to give a valid key to each list item element in our unordered list.

Side Note 🗒️: We’ll get a console error if we don’t give our list item element a key prop. This won’t stop our code from compiling, but it will cause problems for us in the future.

React key prop error screenshot

As React developers, we’ll likely render really big lists of data to our user interface (UI) that will need to be sorted or manipulated in some way. Each element in our list needs a unique identifier so React will know when it has been added, changed, or removed from the UI. Unfortunately, the index position of an element in an array isn’t quite unique enough as that has the potential to change if we decide to add more elements to the middle of our users array later on.

Ideally, each user object in your users array has a key in it called id. This id has possibly been programmatically added to each user object where all of our users are stored somewhere in database. In that case, the id will never change and is always unique. We can pass that as the value to the key prop on our list item element instead:

    const users = [{ id: '1', name: 'Pilar' }, { id: '2', name: 'Helmut' }];
    const App = () => {
      return (
        <ul>
          {users.map((item) => (
            <li key={[user.id](http://user.id/)}>{[user.name](http://user.name/)}</li>
          ))}
        </ul>
      );
    };

Props Drilling

Props drilling is an unofficial term that refers to a parent component passing data through several child components to serve said data to a deeply-nested component. Let’s say, for example, we have an application that allows users to pick either a light or dark theme. In that case, we may have some JSX that looks like this…

    const App = () => {
      return (
        <div>
          <Navbar theme={theme} />
        </div>
      );
    };

    const Navbar = (props) => {
      const { theme } = props;
      return (
        <div>
          <Button theme={theme} label="Button 1" />
          <Button theme={theme} label="Button 2" />
        </div>
      );
    };

    const Button = (props) => {
      const { theme, label } = props;
      return (
        <button
          style={{ backgroundColor: theme === 'dark' ? '#000000' : '#FFFFFF' }}
        >
          {label}
        </button>
      );
    };

Our App component acts as the parent to the Navbar and Button components. Both of its children are being passed a prop called theme that will ultimately change the background color of our Button component.

This approach is reasonable and there is nothing inherently wrong with it! However, our code is using the props drilling approach, meaning we have to explicitly pass our theme prop through the entire component tree 😵‍💫🥴! Our Navbar component doesn’t need the theme prop. In this case, it’s simply a vehicle for passing the theme prop to our Button component.

Why exactly is this a problem, though? Our application is small for now, but at some point our component tree will become larger and more complex. In turn, this will make passing data by way of props drilling super unmanageable.

We can avoid this by using a state management library like React Redux, but that’s considered a heavy-handed approach, especially for a small application, and it should only be reserved for storing and managing truly “global” data like an authenticated user.

A much better solution is React Context, a built-in solution designed to share data among components. Let’s refactor our application and implement React Context. Our code should look like this now:

    const ThemeContext = React.createContext('light');

    const App = () => {
      return (
        <ThemeContext.Provider value="dark">
          <Navbar />
        </ThemeContext.Provider>
      );
    };

    const Navbar = () => {
      return (
        <div>
          <Button label="Button 1" />
          <Button label="Button 2" />
        </div>
      );
    };

    const Button = (props) => {
      const { label } = props;
      return (
        <ThemeContext.Consumer>
          {(theme) => (
            <button
              style={{ backgroundColor: theme === 'dark' ? '#000000' : '#FFFFFF' }}
            >
              {label}
            </button>
          )}
        </ThemeContext.Consumer>
      );
    };

Now we’re providing theme data to our Button component through the use of a context provider and context consumer. React.createContext allows us to create, name and set a default theme for our new context called ThemeContext.

Notice that our Navbar is now nested inside of a context provider, aptly named <ThemeContext.Provider>. This relieves our Navbar of the responsibility of passing the theme prop to our Button component. Providers are used to pass a context value to the components nested within. Any component nested inside of a provider can read its values no matter how deeply nested it is.

Ok, let’s move on to the Button component now! We’re still able to provide theme data to our component by using a context consumer which in our case is called <ThemeContext.Consumer> . Consumers are used to read a context value. These require a function as a child that takes the current context value, in our case theme, and returns JSX.

In conclusion, avoiding these common anti-patterns will help you write more maintainable and scalable React applications. Always strive for simplicity and clarity, and be mindful of the trade-offs involved in using certain patterns and approaches.

Are there any anti-patterns I missed? 📣 Shout them out in the comments below. Happy programming!

blog comments powered by Disqus
Times
Check

Success!

Times

You're already subscribed

Times