React Development: 5 Common React Mistakes and How to Avoid Them

React has revolutionized the way we build user interfaces for web applications. Its component-based architecture, virtual DOM, and thriving ecosystem of libraries and tools have made it a top choice for front-end development. However, as with any technology, even the most experienced developers can stumble into pitfalls.

In this post, we'll uncover five common React mistakes and provide practical tips to write better code. Whether you're a newcomer or a seasoned pro, let's elevate your React skills and avoid these mistakes!

Mistake 1#: Using && operator instead of ternary in JSX

Perhaps you initially thought that there was nothing wrong with using the modern && operator. But imagine we want to display a to-do list that contains all the to-do items. However, if the items array is empty, we prefer not to render the component.

import React from 'react';
import TodoList from './TodoList';

function App() {
   const [items, setItems] = useState([]);

   return (
     <div>
       {items.length && <TodoList items={items} />}
     </div>
   );
}


export default App;

At first look, this looks like a good solution. But what do you think the result will be if the items array is empty? Let's run the code in CodeSandbox and see the result.

Link to Codesandbox: https://codesandbox.io/s/react-mistake-1-xxy9mr

Screenshot:

The result might surprise even more experienced React developers. As you can see the answer is 0.

This occurs because items.length evaluates to 0, and as 0 is considered a falsy value in JavaScript, the && operator short-circuits, resulting in the entire expression equating to 0.

The solution for this issue is simple, use the ternary operator instead of &&. It should look like this.

import React from 'react';
import TodoList from './TodoList';

function App() {
  const [items, setItems] = useState([]);

  return (
    <div>
      {items.length ? <TodoList items={items} /> : null}
    </div>
  );
}


export default App;

Let's run the code again in CodeSandbox and see the result. But this time, let's use the ternary operator instead of &&.

Link to Codesandbox: https://codesandbox.io/s/react-solution-1-wlh4ng

Screenshot:

The result is not rendered TodoList component, which was our initial goal.

Mistake 2#: Using state after changing it

It's easy to mistakenly believe that setCount functions like assignment. But under the hood setState is an asynchronous function.

Let’s look at an example:

import { useState } from "react";

function Counter() {
  const [count, setCount] = useState(0);

  const increment = () => {
    setCount(count + 1);
    console.log(count);
  };
}

Let's run this code Codesandbox and see what will be logged in the console after clicking on the increment button.

Link to Codesandbox: https://codesandbox.io/s/react-mistake-2-pm4rc6

Screenshot:

As you can see in the screenshot above the UI displays the updated version, but in the console, we still see 0.

This happens because when we set a new value, it doesn't occur immediately. Typically, it's performed on the next available render. In other words, when we call setCount, we aren't re-assigning a variable. We're scheduling an update.

It's important to note that setting a state is not asynchronous in a way that it returns a promise. So, adding async/await to it or using then won't work, which is another common mistake.

You're probably wondering what we could do in a situation where we want to use the updated state immediately after the setState function. The proper way to achieve this is by using the useEffect hook, as shown in the example below.

import { useState, useEffect } from "react";

function Counter() {
  const [count, setCount] = useState(0);

  const increment = () => {
    setCount(count + 1);
  };

  useEffect(() => {
    console.log(count);
  }, [count]);
}

Link to Codesandbox: https://codesandbox.io/s/xenodochial-payne-tlfvn9

UseEffect hook accepts two arguments. The first one is a callback function, and another one is an optional array of dependencies. UseEffect executes the callback only if the dependencies have changed between renderings.

It is also important to note that the callback function will run on the initial load. So the console will look like this

Mistake 3#: Not using the key (Using index value as a key)

If you already have experience with React. You've likely seen this error before: “Warning: Each child in a list should have a unique "key" prop.”

It will always happen when forget to add keys with a list of elements as in the example below.

import React from 'react';

function ToDoList({ items }) {
  return (
    <ul>
      {items.map((item) => {
        return (
          <li>{item}</li>
        );
      })}
    </ul>
  );
}

export default ToDoList;

Keys help React identify which items have changed, are added, or are removed, so we should always use them. In online tutorials, you can often find recommendations to use an index for the key value as in the example below.

import React from 'react';

function ToDoList({ items }) {
  return (
    <ul>
      {items.map((item, index) => {
        return (
          <li key={index}>{item}</li>
        );
      })}
    </ul>
  );
}

export default ToDoList;

This solution can cause performance issues due to unnecessary re-renders. We can solve this issue by generating unique key values with uuid npm package, or even better by using native crypto.randomUUID method. We should generate unique ids right after fetching data.

Let’s look at an example:

const [data, setData] = useState(null);

async function fetchData() {
  const res = await fetch('/api');
  const json = await res.json();

  const dataWithId = json.data.map(item => {
    return {
      ...item,
      id: crypto.randomUUID(),
    };
  });

  setData(dataWithId);
}

Mistake 4#: Mutating state directly

In the example below, we have functions that try to directly modify a state that contains an array of strings.

function handleAddItem(value) {
  items.push(value);
  setItems(items);
}

The problem is on the second line where we directly attempt to mutate the state. This is considered a significant mistake in React development and can lead to serious bugs that are difficult to trace.

We can resolve this issue by creating a completely new array, as shown in the example below.

function handleAddItem(value) {
  const newItems = [...items, value];
  setItems(newItems);
}

Mistake 5#: Lack of Unit Tests

One common oversight among developers is the failure to create unit tests, a mistake that can have far-reaching consequences. Disregarding this essential practice can greatly affect the stability of your codebase.

The absence of unit tests raises the chances of introducing hidden bugs and complications, leading to reduced development speed and potential challenges for end-users.

The most popular tools for writing unit tests in React are Jest and React testing library.

Conclusion

In conclusion, React stands as a great choice for developing high-performance, and maintainable web applications. Yet, like any tool, it demands a profound understanding of its intricacies and the adoption of best practices to unleash its full potential. By sidestepping the common pitfalls explored throughout this article, you can ensure that your React codebase remains efficient, trustworthy, and manageable.

Bear in mind that evolving into a proficient React developer necessitates an ongoing commitment to learning, consistent practice, and the continual honing of your skills. So, persist in your journey of discovering new concepts, experimenting with diverse strategies, and embarking on fresh projects.