Chak Shun Yu A software engineer with a current focus on frontend and React, located in the Netherlands.

8 common React error messages and how to address them

10 min read 2840

No matter whether you’re a React developer with multiple years of experience or just starting out in the field, it’s guaranteed that you’ll come across error messages at some point. Whether you write code that causes these errors isn’t important — nobody writes perfect code, and we’re lucky that React helps us out by making sure we’re staying on the right track.

However, what is important is your approach to solving these error messages. Coming across them, searching them on Google, and fixing your code based on other people’s experiences is one way.

Another way — and perhaps, a better one — is to understand the details behind the error and why it’s an issue in the first place.

This article will help you understand these details by going over some of the most common React error messages and explaining what they mean, what their consequences are, and how to fix them.

We’ll be covering the following error messages:

This will help you better understand the underlying errors and prevent you from making similar mistakes in the future.

Warning: Each child in a list should have a unique key prop

import { Card } from "./Card";

const data = [
  { id: 1, text: "Lorem ipsum dolor sit amet, consectetur adipiscing elit." },
  { id: 2, text: "Phasellus semper scelerisque leo at tempus." },
  { id: 3, text: "Duis aliquet sollicitudin neque," }
];

export default function App() {
  return (
    <div className="container">
      {data.map((content) => (
        <div className="card">
          <Card text={content.text} />
        </div>
      ))}
    </div>
  );
}

One of the most common things in React development is taking the items of an array and using a component to render them based on the content of the item. Thanks to JSX, we can easily embed that logic into our component using an Array.map function and return the desired components from the callback.

However, it’s also common to receive a React warning in your browser’s console saying that every child in a list should have a unique key prop. You’ll likely run into this warning several times before making it a habit to give each child a unique key prop, especially if you’re less experienced with React. But how do you fix it before you’ve formed the habit?

How to address this

As the warning indicates, you’ll have to add a key prop to the most outer element of the JSX that you’re returning from the map callback. However, there are several requirements for the key that you’re gonna use. The key should be:

  1. Either a string or a number
  2. Unique to that particular item in the list
  3. Representative of that item in the list across renders
export default function App() {
  return (
    <div className="container">
      {data.map((content) => (
        <div key={content.id} className="card">
          <Card text={content.text} />
        </div>
      ))}
    </div>
  );
}

Although your app won’t crash if you don’t adhere to these requirements, it can lead to some unexpected and often unwanted behavior. React uses these keys to determine which children in a list have changed, and use this information to determine which parts of the previous DOM can be reused and which it should recompute when components are re-rendered. Therefore, it’s always advisable to add these keys.

We made a custom demo for .
No really. Click here to check it out.

Prevent usage of Array index in keys

Building upon the previous warning, we’re diving into the equally common ESLint warning regarding the same topic. This warning will often present after you’ve made a habit of including a key prop with the resulting JSX from a list.

import { Card } from "./Card";

// Notice that we don't include pre-generated identifiers anymore.
const data = [
  { text: "Lorem ipsum dolor sit amet, consectetur adipiscing elit." },
  { text: "Phasellus semper scelerisque leo at tempus." },
  { text: "Duis aliquet sollicitudin neque," }
];

export default function App() {
  return (
    <div className="container">
      {data.map((content, index) => (
        <div key={index} className="card">
          <Card text={content.text} />
        </div>
      ))}
    </div>
  );
}

Sometimes, you won’t have a unique identifier attached to your data. An easy fix is to use the index of the current item in the list. However, the problem with using the item’s index in the array as its key is that it’s not representative of that particular item across renders.

Let’s say that we have a list with several items, and that the user interacts with them by removing the second item. For the first item, nothing has changed to its underlying DOM structure; this is reflected in its key, which stays the same, 0.

For the third item and beyond, their content hasn’t changed, so their underlying structure also shouldn’t change. However, the key prop from all the other items will change because the keys are based on the array index. React will assume that they’ve changed and recompute their structure — unnecessarily. This negatively affects performance and can also lead to inconsistent and incorrect states.

How to address this

To solve this, it’s important to remember that keys don’t necessarily have to be identifiers. As long as they’re unique and representative of the resulting DOM structure, whatever key you want to use will work.

export default function App() {
  return (
    <div className="container">
      {data.map((content) => (
        <div key={content.text} className="card">{/* This is the best we can do, but it works */}
          <Card text={content.text} />
        </div>
      ))}
    </div>
  );
}

React Hook useXXX is called conditionally. React Hooks must be called in the exact same order in every component render

We can optimize our code in different ways during development. One such thing you can do is make sure that certain code is only executed in the code branches where the code is necessary. Especially when dealing with code that is time or resource-heavy, this can make a world of difference in terms of performance.

const Toggle = () => {
  const [isOpen, setIsOpen] = useState(false);

  if (isOpen) {
    return <div>{/* ... */}</div>;
  }
  const openToggle = useCallback(() => setIsOpen(true), []);
  return <button onClick={openToggle}>{/* ... */}</button>;
};

Unfortunately, applying this optimization technique to Hooks will present you with the warning to not call React Hooks conditionally, as you must call them in the same order in every component render.

This is necessary because, internally, React uses the order in which Hooks are called to keep track of their underlying states and preserve them between renders. If you mess with that order, React will, internally, not know which state matches with the Hook anymore. This causes major issues for React and can even result in bugs.

How to address this

React Hooks must be always called at the top level of components — and unconditionally. In practice, this often boils down to reserving the first section of a component for React Hook initializations.

const Toggle = () => {
  const [isOpen, setIsOpen] = useState(false);
  const openToggle = useCallback(() => setIsOpen(true), []);

  if (isOpen) {
    return <div>{/* ... */}</div>;
  }
  return <button onClick={openToggle}>{/* ... */}</button>;
};

React Hook has a missing dependency: ‘XXX’. Either include it or remove the dependency array

An interesting aspect of React Hooks is the dependencies array. Almost every React Hook accepts a second argument in the form of an array, inside of which you’re able to define the dependencies for the Hook. When any of the dependencies change, React will detect it and re-trigger the Hook.

In their documentation, React recommends developers to always include all variables in the dependencies array if they’re used in the Hook and affect the component’s rendering when changed.

How to address this

To help with this, it’s recommended to make use of the exhaustive-deps rule inside the react-hooks ESLint plugin. Activating it will warn you when any React Hook doesn’t have all dependencies defined.

const Component = ({ value, onChange }) => {
  useEffect(() => {
    if (value) {
      onChange(value);
    }
  }, [value]); // `onChange` isn't included as a dependency here.

  // ...
}

The reason you should be exhaustive with the dependencies array matters is related to the concept of closures and scopes in JavaScript. If the main callback of the React Hook uses variables outside its own scope, then it can only remember the version of those variables when it was executed.

But when those variables change, the closure of the callback can’t automatically pick up on those changed versions. This can lead to executing your React Hook code with outdated references of its dependencies, and result in different behavior than expected.

For this reason, it’s always recommended to be exhaustive with the dependencies array. Doing so addresses all possible issues with calling React Hooks this way, as it points React towards the variables to keep track of. When React detects changes in any of the variables, it will rerun the callback, allowing it to pick up on the changed versions of the dependencies and run as expected.

Can’t perform a React state update on an unmounted component

When dealing with async data or logic flows in your components, you may encounter a runtime error in your browser’s console telling you that you can’t perform a state update on a component that is already unmounted. The issue is that somewhere in your components tree, a state update is triggered onto a component that is already unmounted.

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

  useEffect(() => {
    fetchAsyncData().then((data) => setData(data));
  }, []);

  // ...
};

This is caused by a state update that is dependent on an async request. The async request starts somewhere in the lifecycle of a component (such as inside a useEffect Hook) but takes a while to complete.

Meanwhile, the component has already been unmounted (due to e.g. user interactions), but the original async request still finishes — because it’s not connected to the React lifecycle — and triggers a state update to the component. The error is triggered here because the component doesn’t exist anymore.

How to address this

There are several ways to address this, all of which boil down to two different concepts. First, it’s possible to keep track of whether the component is mounted, and we can perform actions based on that.

While this works, it’s not recommended. The problem with this method is that it unnecessarily keeps a reference of unmounted components around, which causes memory leaks and performance issues.

const Component = () => {
  const [data, setData] = useState(null);
  const isMounted = useRef(true);

  useEffect(() => {
    fetchAsyncData().then(data => {
      if(isMounted.current) {
        setData(data);
      }
    });

    return () => {
      isMounted.current = false;
    };
  }, []);

  // ...
}

The second — and preferred — way is to cancel the async request when the component unmounts. Some async request libraries will already have a mechanism in place to cancel such a request. If so, it’s as straightforward as cancelling the requst during the cleanup callback of the useEffect Hook.

If you’re not using such a library, you could achieve the same using AbortController. The only downsides to these cancel methods are that they are fully reliant on a library’s implementation or browser support.

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

  useEffect(() => {
    const controller = new AbortController();
    fetch(url, { signal: controller.signal }).then((data) => setData(data));
    return () => {
      controller.abort();
    }
  }, []);

  // ...
};

Too many re-renders. React limits the number of renders to prevent an infinite loop

Infinite loops are the bane of every developer’s existence and React developers are not an exception to this rule. Luckily, React does a very nice job of detecting them and warning you about it before your entire device becomes unresponsive.

How to address this

As the warning suggests, the problem is that your component is triggering too many re-renders. This happens when your component queues too many state updates in a very short amount of time. The most common culprits for causing infinite loops are:

  • Performing state updates directly in the render
  • Not providing a proper callback to an event handler

If you’re running into this particular warning, make sure to check those two aspects of your component.

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

  setCount(count + 1); // State update in the render

  return (
    <div className="App">
      {/* onClick doesn't receive a proper callback */}
      <button onClick={setCount((prevCount) => prevCount + 1)}>
        Increment that counter
      </button>
    </div>
  );
}

Objects are not valid as a React child / Functions are not valid as a React child

In React, there are a lot of things that we can render to the DOM in our components. The choices are almost endless: all the HTML tags, any JSX element, any primitive JavaScript value, an array of the previous values, and even JavaScript expressions, as long as they evaluate to any of the previous values.

Despite that, unfortunately, React still doesn’t accept everything that possibly exists as a React child. To be more specific, you can’t render objects and functions to the DOM because these two data values will not evaluate to anything meaningful that React can render into the DOM. Therefore, any attempts to do so will result in React complaining about it in the form of the mentioned errors.

How to address this

If you’re facing either of these errors, it’s recommended to verify that the variables that you’re rendering are the expected type. Most often, this issue is caused by rendering a child or variable in JSX, assuming it’s a primitive value — but, in reality, it turns out to be an object or a function. As a prevention method, having a type system in place can significantly help.

const Component = ({ body }) => (
  <div>
    <h1>{/* */}</h1>
    {/* Have to be sure the `body` prop is a valid React child */}
    <div className="body">{body}</div>
  </div>
);

Adjacent JSX elements must be wrapped in an enclosing tag

One of React’s biggest benefits is being able to construct an entire application by combining a lot of smaller components. Every component can define its piece of UI in the form of JSX that it should render, which ultimately contributes to the application’s entire DOM structure.

const Component = () => (
  <div><NiceComponent /></div>
  <div><GoodComponent /></div>
);

Due to React’s compounding nature, a common thing to try is returning two JSX elements in the root of a component that is only used inside another component. However, doing so will surprisingly present React developers with a warning telling them they have to wrap adjacent JSX elements in enclosing tags.

From the perspective of the average React developer, this component will only be used inside of another component. So, in their mental model, it makes perfect sense to return two elements from a component because the resulting DOM structure would be the same, no matter whether an outer element is defined in this component or the parent component.

However, React isn’t able to make this assumption. Potentially, this component could be used in the root and break the application, as it will result in an invalid DOM structure.

How to address this

React developers should always wrap multiple JSX elements returned from a component in an enclosing tag. This can be an element, a component, or React’s Fragment, if you’re sure that the component doesn’t require an outer element.

const Component = () => (
  <React.Fragment>
    <div><NiceComponent /></div>
    <div><GoodComponent /></div>
  </React.Fragment>
);

Final thoughts

Coming across errors during development is an inevitable part of the process, no matter the amount of experience you have. However, the way you handle these error messages is also indicative of your ability as a React developer. To do so properly, it’s necessary to understand these errors and know why they’re happening.

To help you with this, this article went over eight of the most common React error messages that you’ll encounter during React development. we covered the meaning behind the error messages, the underlying error, how to address the error, and what happens if you don’t fix the errors.

With this knowledge, you should now understand these errors more thoroughly and feel empowered to write less code that contains these bugs, leading to higher quality code.

Full visibility into production React apps

Debugging React applications can be difficult, especially when users experience issues that are hard to reproduce. If you’re interested in monitoring and tracking Redux state, automatically surfacing JavaScript errors, and tracking slow network requests and component load time, try LogRocket.

LogRocket is like a DVR for web and mobile apps, recording literally everything that happens on your React app. Instead of guessing why problems happen, you can aggregate and report on what state your application was in when an issue occurred. LogRocket also monitors your app's performance, reporting with metrics like client CPU load, client memory usage, and more.

The LogRocket Redux middleware package adds an extra layer of visibility into your user sessions. LogRocket logs all actions and state from your Redux stores.

Modernize how you debug your React apps — .

Chak Shun Yu A software engineer with a current focus on frontend and React, located in the Netherlands.

One Reply to “8 common React error messages and how to address…”

  1. There is a nice shorthand for using … around adjacent JSX elements: Use … instead. It does the same thing, with less typing :-).

Leave a Reply