Fix: React TypeError: Cannot read property 'map' of undefined
Part of: React & Frontend Errors
Quick Answer
How to fix React TypeError Cannot read property map of undefined caused by uninitialized state, async data loading, wrong API response structure, and missing default values.
The Error
Your React app crashes with:
TypeError: Cannot read property 'map' of undefinedOr the modern equivalent:
TypeError: Cannot read properties of undefined (reading 'map')You called .map() on a value that is undefined instead of an array. This typically happens when rendering a list from state or props that has not been initialized or loaded yet.
Why This Happens
.map() is an array method. When you call someVar.map(...), JavaScript first evaluates someVar. If it is undefined (or null), accessing .map on it throws a TypeError.
The crash is almost always a timing problem: the component renders once before the data arrives. React’s render cycle is synchronous, but the network request that fills your state is asynchronous. The very first render runs with whatever the initial state was. If that initial state was undefined, the .map() call in JSX runs before any data exists. The fix is either to make the initial state safe (an empty array) or to guard the render with a conditional check.
The second common shape is a shape mismatch: the response arrived but it is not the structure you assumed. REST APIs frequently wrap arrays inside an envelope ({ data: [...] }, { results: [...] }, { items: [...] }, { data: { users: [...] } }). If you set state to the envelope and then call .map on it, the envelope is an object — and an object has no .map. The error message reads the same way in both cases, so always log the actual response shape during debugging.
In React, this commonly occurs because:
- State initialized without a default array.
useState()without an initial value defaults toundefined. - Data not loaded yet. The component renders before the API call completes.
- API response structure changed. The data is nested differently than expected.
- Prop not passed. A parent component did not pass the expected array prop.
- Conditional data. The data exists in some cases but not others.
Platform and Environment Differences
The same .map of undefined crash surfaces in noticeably different ways depending on where your React code runs.
SSR (Next.js, Remix) vs CSR. In a pure client-side React app, the first render always runs in the browser, so a null initial state plus a guard works fine. With Next.js App Router or Remix, the component first renders on the server during the request. If the server fetch returns undefined (or your loader returns the wrong key), the error throws during SSR and you get a 500 response, not a runtime crash in DevTools. Next.js wraps it as “An error occurred in the Server Component” with the underlying Cannot read properties of undefined in the server logs. Always read the server log when SSR is involved — the browser only sees the rendered error page.
Browser-only globals during build. Code that reads window.something.items or localStorage.list runs on the server during SSR/SSG and crashes with Cannot read properties of undefined. Wrap browser-only access in typeof window !== "undefined" checks or move the access into useEffect. The hydration mismatch errors that show up next come from the same root cause.
Node vs Deno vs Bun when running React tests. Jest under Node sets globalThis.window only when you configure jsdom as the test environment. If you run a component test under the default node environment, any code that touches window.someState returns undefined and .map throws. Vitest behaves the same way but uses environment: "jsdom" in its config. Deno’s --unstable-bare-node-builtins flag changes how Node modules resolve, and Bun’s test runner ships its own --bun DOM implementation that differs subtly from jsdom. The same component can render in the browser, render in Jest with jsdom, and throw under Bun’s runner.
Mobile webviews. iOS Safari and Android Chrome differ in how they handle Intl, BigInt, optional chaining transpilation, and certain fetch defaults. A response that parses to { data: [...] } on desktop Chrome might parse to undefined on iOS Safari if the response uses a content type that triggers WebKit’s strict JSON parser. WKWebView under React Native also has different cookie and CORS behavior, which can cause fetch to silently return without setting state — your component renders with the initial undefined and crashes.
React Server Components. With RSC, the component runs on the server and streams to the client. An undefined result from a server-side fetch crashes at the server boundary. You will not see this error in the browser console — it appears in the server logs and the affected component tree falls back to the nearest error boundary or root error page.
Fix 1: Initialize State with an Empty Array
The most common fix. Give your state a default empty array:
Broken:
function UserList() {
const [users, setUsers] = useState(); // undefined!
return (
<ul>
{users.map(user => <li key={user.id}>{user.name}</li>)}
</ul>
);
}Fixed:
function UserList() {
const [users, setUsers] = useState([]); // Empty array — .map() works
return (
<ul>
{users.map(user => <li key={user.id}>{user.name}</li>)}
</ul>
);
}useState([]) starts with an empty array. .map() on an empty array returns an empty array — no error, no rendered items. When data loads, the state updates and the list renders.
Pro Tip: Always match your state’s initial value to its expected type. If the state should be an array, initialize with
[]. If an object, initialize with{}ornull(and handle thenullcase).
Fix 2: Add a Loading Guard
Check if data exists before rendering:
function UserList() {
const [users, setUsers] = useState(null);
useEffect(() => {
fetch("/api/users")
.then(res => res.json())
.then(setUsers);
}, []);
if (!users) return <p>Loading...</p>;
return (
<ul>
{users.map(user => <li key={user.id}>{user.name}</li>)}
</ul>
);
}The if (!users) guard prevents rendering the list before data is available. This also provides a better user experience with a loading indicator.
With explicit loading state:
const [users, setUsers] = useState([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
fetch("/api/users")
.then(res => res.json())
.then(data => {
setUsers(data);
setLoading(false);
});
}, []);
if (loading) return <p>Loading...</p>;Fix 3: Use Optional Chaining
Use ?. to safely access the array before calling .map():
{users?.map(user => <li key={user.id}>{user.name}</li>)}If users is undefined or null, the entire expression evaluates to undefined — which React ignores (renders nothing). No error.
With a fallback:
{(users ?? []).map(user => <li key={user.id}>{user.name}</li>)}The ?? (nullish coalescing) operator returns [] if users is null or undefined.
Fix 4: Fix API Response Structure
The API might return data in a nested structure:
{
"data": {
"users": [
{"id": 1, "name": "Alice"},
{"id": 2, "name": "Bob"}
]
},
"meta": {
"total": 2
}
}Broken — accessing the wrong level:
const [users, setUsers] = useState([]);
useEffect(() => {
fetch("/api/users")
.then(res => res.json())
.then(data => setUsers(data)); // data is the whole response object!
}, []);
// users.map() fails because users is {data: {users: [...]}, meta: {...}}Fixed — extract the array:
useEffect(() => {
fetch("/api/users")
.then(res => res.json())
.then(response => setUsers(response.data.users)); // Extract the array
}, []);Debug the response shape:
useEffect(() => {
fetch("/api/users")
.then(res => res.json())
.then(data => {
console.log("API response:", data);
console.log("Is array?", Array.isArray(data));
setUsers(data);
});
}, []);For rendering objects directly in JSX, see Fix: Objects are not valid as a React child.
Common Mistake: Assuming the API always returns an array at the top level. Most REST APIs wrap arrays in an object (
{ data: [...] },{ results: [...] },{ items: [...] }). Always check the actual response structure.
Fix 5: Set Default Prop Values
If the array comes from props, provide a default:
Broken:
function ItemList({ items }) {
return (
<ul>
{items.map(item => <li key={item.id}>{item.name}</li>)}
</ul>
);
}
// Parent forgets to pass items:
<ItemList />Fixed — default parameter:
function ItemList({ items = [] }) {
return (
<ul>
{items.map(item => <li key={item.id}>{item.name}</li>)}
</ul>
);
}Fixed — with TypeScript:
interface Props {
items?: Item[];
}
function ItemList({ items = [] }: Props) {
return (
<ul>
{items.map(item => <li key={item.id}>{item.name}</li>)}
</ul>
);
}The items = [] default handles both undefined and missing props.
Fix 6: Fix Context and Redux State
If the array comes from context or Redux:
Broken — context value not initialized:
const DataContext = createContext(); // undefined by default!
function ItemList() {
const { items } = useContext(DataContext); // items is undefined
return items.map(...); // TypeError!
}Fixed — provide a default value:
const DataContext = createContext({ items: [] });Redux — handle initial state:
const initialState = {
users: [], // Always initialize arrays
loading: false,
error: null,
};
function usersReducer(state = initialState, action) {
switch (action.type) {
case "FETCH_SUCCESS":
return { ...state, users: action.payload, loading: false };
default:
return state;
}
}Fix 7: Handle Error States
API calls can fail, leaving state as the initial value:
const [users, setUsers] = useState([]);
const [error, setError] = useState(null);
useEffect(() => {
fetch("/api/users")
.then(res => {
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return res.json();
})
.then(data => setUsers(data))
.catch(err => setError(err.message));
}, []);
if (error) return <p>Error: {error}</p>;
return (
<ul>
{users.map(user => <li key={user.id}>{user.name}</li>)}
</ul>
);Without error handling, a failed API call leaves users as [] (if initialized correctly) or could set it to an unexpected value.
For useEffect dependency issues that cause infinite loops during data fetching, see Fix: React useEffect infinite loop.
Fix 8: Use Array.isArray() for Type Safety
When you are not sure if a value is an array:
function SafeList({ data }) {
const items = Array.isArray(data) ? data : [];
return (
<ul>
{items.map(item => <li key={item.id}>{item.name}</li>)}
</ul>
);
}Array.isArray() returns true only for actual arrays, not for objects, strings, or other types that might have a .map property.
Still Not Working?
Check for race conditions. If multiple state updates happen quickly, the array might be temporarily set to undefined:
// This can cause a flash of undefined:
setUsers(undefined); // Oops
setUsers(newData); // Fixed — but the first render might crashCheck for data transformation bugs. A .filter() or .find() in the chain might return undefined:
// .find() returns undefined if no match
const activeUsers = users.find(u => u.active);
activeUsers.map(...) // TypeError if no active users found!
// Fix: Use .filter() which always returns an array
const activeUsers = users.filter(u => u.active);
activeUsers.map(...) // Works — empty array if no matchesCheck for stale closures. An async operation might capture an old reference to state. A captured state from a previous render can resolve after a fresh render replaced the array with undefined, and the late callback then writes the wrong value back.
Check for SSR rendering before data loads. In Next.js App Router, a Client Component starts with the initial state on the server and re-renders on the client. If your fetch only runs in useEffect, the SSR pass sees undefined. Move the fetch into a Server Component or a loader (Remix) so the data is present on the first render. See Fix: Next.js params should be awaited for async data patterns specific to the App Router.
Check for double-render under React Strict Mode. In development, Strict Mode invokes function components twice and runs effects twice. If your effect mutates external state in a way that briefly sets it to undefined, the second render can crash where the first one would not. The fix is to make state transitions idempotent — never go from “real array” back through undefined.
Check for transformations that mutate in place. Methods like .sort() and .reverse() mutate the array. If a parent passes the same array reference and you call .sort() inside a child, the parent’s useMemo may still hold a stale snapshot and one render path can land on undefined while React reconciles.
Check for JSON.parse returning non-arrays. Reading from localStorage and parsing the result returns whatever was stored. If the stored value is the string "null", you get null. If it is empty, JSON.parse("") throws. Always guard with a try/catch and a default.
For the general version of this error (not specific to .map), see Fix: TypeError: Cannot read properties of undefined. For the closely related warning when keys are missing on rendered list items, the cause and fix sit in the same render path described above.
Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.
Was this article helpful?
Related Articles
Fix: AutoAnimate Not Working — Transitions Not Playing, List Items Not Animating, or React State Changes Ignored
How to fix @formkit/auto-animate issues — parent ref setup, React useAutoAnimate hook, Vue directive, animation customization, disabling for specific elements, and framework integration.
Fix: Blurhash Not Working — Placeholder Not Rendering, Encoding Failing, or Colors Wrong
How to fix Blurhash image placeholder issues — encoding with Sharp, decoding in React, canvas rendering, Next.js image placeholders, CSS blur fallback, and performance optimization.
Fix: Docusaurus Not Working — Build Failing, Sidebar Not Showing, or Plugin Errors
How to fix Docusaurus issues — docs and blog configuration, sidebar generation, custom theme components, plugin setup, MDX compatibility, search integration, and deployment.
Fix: Embla Carousel Not Working — Slides Not Scrolling, Autoplay Not Starting, or Thumbnails Not Syncing
How to fix Embla Carousel issues — React setup, slide sizing, autoplay and navigation plugins, loop mode, thumbnail carousels, responsive breakpoints, and vertical scrolling.