Fix the "Cannot read property 'map' of undefined" Error in React
Nick Scialli
February 21, 2021
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!
Nick Scialli is a senior UI engineer at Microsoft.