Fix "Cannot read property 'map' of undefined" — Every Cause with Solutions
TypeError: Cannot read properties of undefined (reading 'map') is one of the most common JavaScript and React runtime errors. It means you called .map() on a value that isundefined or null. This guide covers every cause — async data loading, wrong API shape, missing props, nested object access, and object vs array confusion — with the exact fix for each case, plus a universal safe map utility and TypeScript patterns to prevent this error entirely.
#1
most common React runtime error in production
undefined
the value you're calling .map() on
6 causes
all covered with exact fixes and code examples
?.operator
optional chaining prevents most cases instantly
The Root Cause — Why This Error Happens
The core issue
.map() is an Array method. If the variable is undefined,null, a string, a number, or a plain object, JavaScript throws this TypeError. The fix is always the same: ensure the value is actually an array before calling .map(). The question is just why it became undefined in the first place.
The exact error messages
Cause 1 — Data Not Loaded Yet (Async React)
The most common cause in React. The component renders before the async fetch completes — so on the first render, the state variable is still at its initial value (often undefined). The fix: always initialize array state with an empty array [], never with undefined.
Always initialize array state with [] not undefined
undefined initial state → crash on first render
// ❌ products is undefined on first render (before fetch completes)
function ProductList() {
const [products, setProducts] = useState(); // initial state = undefined
// React renders immediately with this undefined value
useEffect(() => {
fetch('/api/products')
.then(res => res.json())
.then(data => setProducts(data)); // sets state AFTER first render
}, []);
return (
<ul>
{products.map(p => <li key={p.id}>{p.name}</li>)} // ❌ CRASH on first render
</ul>
);
}[] initial state → safe on all renders
// ✅ Empty array initial state → safe on first render
function ProductList() {
const [products, setProducts] = useState([]); // [] not undefined
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
fetch('/api/products')
.then(res => {
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return res.json();
})
.then(data => {
setProducts(Array.isArray(data) ? data : data.items ?? []);
setLoading(false);
})
.catch(err => { setError(err.message); setLoading(false); });
}, []);
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error}</div>;
if (!products.length) return <div>No products found.</div>;
return (
<ul>
{products.map(p => <li key={p.id}>{p.name}</li>)} // ✅ always works
</ul>
);
}Cause 2 — API Returns Unexpected Shape
You expected an array directly but the API wraps it: { data: [...], total: 100 }. The fix: always check the actual API response shape before assuming it's an array.
Check actual API shape — don't assume array at root
Assume API returns array at root
// ❌ Assumed API returns array, but it returns an object
const response = await fetch('/api/products');
const products = await response.json();
// Actual response: { data: [...], total: 100, page: 1 }
// But 'products' is the whole object, not the array
products.map(p => p.name); // ❌ products is an object, not arrayHandle multiple possible API shapes with explicit extraction
// ✅ Inspect response before assuming shape
const response = await fetch('/api/products');
const json = await response.json();
// Log in development to see actual shape:
if (process.env.NODE_ENV === 'development') {
console.log('API response shape:', JSON.stringify(json).slice(0, 200));
}
// Safely extract array regardless of API wrapping:
const products = Array.isArray(json) ? json // direct array
: Array.isArray(json.data) ? json.data // { data: [...] }
: Array.isArray(json.items) ? json.items // { items: [...] }
: Array.isArray(json.results) ? json.results // { results: [...] }
: []; // fallback — log a warning
if (!products.length && json) {
console.warn('Could not find array in API response:', Object.keys(json));
}
products.map(p => p.name); // ✅ always an arrayCause 3 — Props Not Passed or Missing Default
Always provide default values for array props
No default value — crashes when prop not passed
// ❌ Parent forgets to pass products prop
function Parent() {
return <ProductList />; // no products prop = undefined in child
}
function ProductList({ products }) {
// products = undefined (prop was never passed)
return products.map(p => <div key={p.id}>{p.name}</div>); // ❌ crash
}Default value or guard clause prevents crash
// ✅ Option 1: Default parameter value (recommended)
function ProductList({ products = [] }) {
return products.map(p => <div key={p.id}>{p.name}</div>); // always works
}
// ✅ Option 2: Guard clause
function ProductList({ products }) {
if (!Array.isArray(products)) return null; // or return <EmptyState />
return products.map(p => <div key={p.id}>{p.name}</div>);
}
// ✅ Option 3: TypeScript to catch at compile time (preferred)
interface ProductListProps {
products: Product[]; // required — TypeScript error if parent doesn't pass it
}
function ProductList({ products }: ProductListProps) {
return products.map(p => <div key={p.id}>{p.name}</div>);
}
// ✅ Option 4: PropTypes for runtime validation
import PropTypes from 'prop-types';
ProductList.propTypes = {
products: PropTypes.arrayOf(PropTypes.object).isRequired,
};Cause 4 — Nested Object Access
Use optional chaining for nested property access
No optional chaining — crashes on any undefined in chain
// ❌ Nested property may not exist
const user = { name: 'Alice' }; // no 'orders' property
user.orders.map(order => order.id); // ❌ user.orders is undefined
// ❌ Deeply nested
response.data.users.map(u => u.email); // fails if any level is undefinedOptional chaining handles any undefined in the chain safely
// ✅ Option 1: Optional chaining (modern, recommended)
user.orders?.map(order => order.id) ?? [];
// ✅ Option 2: Nullish coalescing with fallback
(user.orders ?? []).map(order => order.id);
// ✅ Option 3: Explicit guard
if (user.orders && Array.isArray(user.orders)) {
user.orders.map(order => order.id);
}
// ✅ Deeply nested with optional chaining
(response?.data?.users ?? []).map(u => u.email);
// ✅ In JSX:
{user?.orders?.map(order => (
<div key={order.id}>{order.name}</div>
)) ?? <p>No orders</p>}Cause 5 — Object Instead of Array
Objects don't have .map() — convert to array first
.map() on an object → TypeError
// ❌ .map() called on a plain object
const userMap = { alice: {id: 1}, bob: {id: 2} };
userMap.map(user => user.id); // TypeError: userMap.map is not a function
// ❌ JSON response parsed as object when you expected array
const data = JSON.parse('{"user1": {...}, "user2": {...}}');
data.map(u => u.name); // TypeErrorObject.values() → array → .map()
// ✅ Convert object to array using Object methods
const userMap = { alice: {id: 1}, bob: {id: 2} };
Object.values(userMap).map(user => user.id); // [1, 2]
Object.keys(userMap).map(key => userMap[key].id); // [1, 2]
Object.entries(userMap).map(([key, val]) => val.id); // [1, 2]
// ✅ In JSX — mapping over object values:
{Object.values(userMap).map(user => (
<div key={user.id}>{user.name}</div>
))}Cause 6 — Redux/Context State Not Initialized
Initialize Redux slice array state correctly
undefined initial Redux state
// ❌ Redux slice initial state is undefined
const productsSlice = createSlice({
name: 'products',
initialState: {
items: undefined, // ❌ will be undefined until first fetch
loading: false,
},
// ...
});
// Component:
const items = useSelector(state => state.products.items);
items.map(item => item.id); // ❌ crash before first fetch[] initial state + nullish coalescing in selector
// ✅ Redux slice with array initial state
const productsSlice = createSlice({
name: 'products',
initialState: {
items: [], // ✅ always an array
loading: false,
error: null,
},
// ...
});
// Component with safe selector:
const items = useSelector(state => state.products.items ?? []);
items.map(item => item.id); // ✅ always worksUniversal Safe .map() Utility
// ─── JavaScript safeMap ──────────────────────────────────────────────────────
function safeMap(value, callback, fallback = []) {
if (value === null || value === undefined) return fallback;
if (!Array.isArray(value)) {
console.warn('[safeMap] Expected array, got:', typeof value, value);
return fallback;
}
return value.map(callback);
}
// Usage — never throws even if data is undefined/null/object
const ids = safeMap(apiResponse?.data, item => item.id);
const names = safeMap(user?.orders, order => order.name, ['No orders']);
// ─── Inline alternatives (no utility needed) ─────────────────────────────────
const ids = apiResponse?.data?.map(item => item.id) ?? [];
const ids = (apiResponse?.data ?? []).map(item => item.id);
const ids = Array.isArray(data) ? data.map(item => item.id) : [];
// ─── TypeScript: prevent at compile time ─────────────────────────────────────
// Using strict types forces callers to handle undefined explicitly
function mapItems<T, R>(
items: T[] | undefined | null,
callback: (item: T) => R
): R[] {
return (items ?? []).map(callback);
}
// Now TypeScript will error if you forget the safe access pattern
// mapItems(undefined, x => x); // returns []
// mapItems(null, x => x); // returns []
// mapItems([1,2,3], x => x*2); // returns [2,4,6]
// ─── React custom hook for async arrays ──────────────────────────────────────
function useApiArray<T>(url: string): { data: T[]; loading: boolean; error: string | null } {
const [data, setData] = useState<T[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
fetch(url)
.then(r => r.json())
.then(json => setData(Array.isArray(json) ? json : json.data ?? []))
.catch(e => setError(e.message))
.finally(() => setLoading(false));
}, [url]);
return { data, loading, error };
}
// Usage:
const { data: products, loading } = useApiArray<Product>('/api/products');
// products is always a Product[] — never undefinedDebugging Checklist
Log the value before .map()
Add console.log("value:", typeof myValue, myValue) immediately before the .map() call. This shows the actual type and value.
Check initial state
If in React: is the useState() initialized with [] or undefined? Always use [] for arrays.
Check the API response shape
Open DevTools → Network → find the request → Preview tab. Is the response an array or an object wrapping an array? Extract the nested array.
Check prop passing
In React DevTools, inspect the component. Is the prop value undefined? Did the parent forget to pass it, or pass the wrong variable name?
Add Array.isArray() guard
Wrap in: if (!Array.isArray(value)) { console.error("not array:", value); return null; } as a temporary diagnostic.