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

1

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

TypeError: Cannot read properties of undefined (reading 'map') — Chrome, Node.js 16+ TypeError: Cannot read property 'map' of undefined — Chrome, Node.js <16 (older format) TypeError: undefined is not an object (evaluating 'items.map') — Safari TypeError: items is undefined — Firefox
2

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

❌ Bad
// ❌ 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

✅ Good
// ✅ 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>
  );
}
3

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

❌ Bad
// ❌ 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 array

Handle multiple possible API shapes with explicit extraction

✅ Good
// ✅ 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 array
4

Cause 3 — Props Not Passed or Missing Default

Always provide default values for array props

No default value — crashes when prop not passed

❌ Bad
// ❌ 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

✅ Good
// ✅ 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,
};
5

Cause 4 — Nested Object Access

Use optional chaining for nested property access

No optional chaining — crashes on any undefined in chain

❌ Bad
// ❌ 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 undefined

Optional chaining handles any undefined in the chain safely

✅ Good
// ✅ 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>}
6

Cause 5 — Object Instead of Array

Objects don't have .map() — convert to array first

.map() on an object → TypeError

❌ Bad
// ❌ .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); // TypeError

Object.values() → array → .map()

✅ Good
// ✅ 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>
))}
7

Cause 6 — Redux/Context State Not Initialized

Initialize Redux slice array state correctly

undefined initial Redux state

❌ Bad
// ❌ 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

✅ Good
// ✅ 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 works
8

Universal Safe .map() Utility

javascriptsafeMap utility + TypeScript version
// ─── 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 undefined
9

Debugging Checklist

1

Log the value before .map()

Add console.log("value:", typeof myValue, myValue) immediately before the .map() call. This shows the actual type and value.

2

Check initial state

If in React: is the useState() initialized with [] or undefined? Always use [] for arrays.

3

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.

4

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?

5

Add Array.isArray() guard

Wrap in: if (!Array.isArray(value)) { console.error("not array:", value); return null; } as a temporary diagnostic.

Frequently Asked Questions