TypeOfNaN

Fix the "Cannot read property 'map' of undefined" Error in React

Nick Scialli
February 21, 2021

New — Check out my free newsletter on how the web works!

This is one of the more common errors you will run into when starting out with React:

Cannot read property 'map' of undefined

In this post, we’ll learn how to fix it.

Why It’s Happening

The variable you are trying to map over is undefined. It will probably eventually be an array, but due to the asynchronous nature of React, you are experiencing at least one render when the variable is undefined.

Let’s take this example code. In it, we fetch some data from an API and set state with that data.

function MyComponent() {
  const [data, setData] = useState();

  useEffect(() => {
    fetch('/api/data')
      .then((res) => res.json())
      .then((data) => {
        setData(data);
      })
      .catch((err) => {
        console.log(err);
      });
  }, []);

  return (
    <ul>
      {data.map((item) => (
        <li key={item.id}>{item.name}</li>
      ))}
    </ul>
  );
}

This code might seem fine, but our data fetching takes some time and React does not wait for the data fetching (or any async action) to happen before it first renders your JSX. That means, while the data is being fetched, React is trying to run data.map(...).

Since we provided no initial value for data in our useState hook, data is undefined. As we know from the error message, it’s problematic to try to call map on undefined!

Fix Option 1: Default the Variable to an Empty Array

This first quick fix might be enough for your use case: just default your stateful variable to an array while you’re waiting for your data to fetch. For example:

function MyComponent() {
  // Empty array in useState!
  const [data, setData] = useState([]);

  useEffect(() => {
    fetch('/api/data')
      .then((res) => res.json())
      .then((data) => {
        setData(data);
      })
      .catch((err) => {
        console.log(err);
      });
  }, []);

  return (
    <ul>
      {data.map((item) => (
        <li key={item.id}>{item.name}</li>
      ))}
    </ul>
  );
}

The reason this works is that, while your data fetching is happening, React will call the map method on an empty data array. This is fine—nothing will be rendered and there will be no errors. Once the data loads from the API call, our data state will be set and our list will correctly render.

Fix Option 2: Showing a Loading Indicator

While the previous fix is simple, it might not be the best user experience to display nothing until the data loads. Instead, we might choose to display a loading indicator. There are a few ways we can do this, but one of the simpler ways is to just add another stateful variable called loading.

function MyComponent() {
  const [data, setData] = useState([]);
  const [loading, setLoading] = useState(false);

  useEffect(() => {
    setLoading(true);
    fetch('/api/data')
      .then((res) => res.json())
      .then((data) => {
        setData(data);
      })
      .catch((err) => {
        console.log(err);
      })
      .finally(() => {
        setLoading(false);
      });
  }, []);

  if (loading) {
    return <p>Data is loading...</p>;
  }

  return (
    <ul>
      {data.map((item) => (
        <li key={item.id}>{item.name}</li>
      ))}
    </ul>
  );
}

This is pretty simple and effective! When our data starts to fetch, we set loading to true. When it’s done fetching, we set loading to false. Note that we use the finally method on our Promise since that will run regardless of whether the fetch succeeds or fails.

Speaking of Fetch Failures…

We should probably handle the situation in which our fetch fails. Additionally, we can show the user an error message if our data variable is not an array. This latter point is important in making sure that we never try to access the map property on a non-array since it simply won’t work.

function MyComponent() {
  const [data, setData] = useState([]);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState();

  useEffect(() => {
    setLoading(true);
    fetch('/api/data')
      .then((res) => res.json())
      .then((data) => {
        setData(data);
      })
      .catch((err) => {
        setError(err);
      })
      .finally(() => {
        setLoading(false);
      });
  }, []);

  if (loading) {
    return <p>Data is loading...</p>;
  }

  if (error || !Array.isArray(data)) {
    return <p>There was an error loading your data!</p>;
  }

  return (
    <ul>
      {data.map((item) => (
        <li key={item.id}>{item.name}</li>
      ))}
    </ul>
  );
}

And now we have a pretty safe way of handling our async operation without getting the dreaded “cannot read property ‘map’ of undefined” error!

🎓 Learn how the web works

One of the best ways to level up your tech career is to have a great foundational understanding of how the web works. In my free newsletter, How the Web Works, I provide simple, bite-sized explanations for various web topics that can help you boost your knowledge. Join 2,500+ other learners on the newsletter today!

Signing up is free, I never spam, and you can unsubscribe any time. You won't regret it!

Sign up for the newsletter »
Nick Scialli

Nick Scialli is a senior UI engineer at Microsoft.

© 2024 Nick Scialli