Fix TypeError: Cannot read properties of undefined (reading 'map') in React

beginnerโš›๏ธ React2026-03-19| React 16+, Node.js 14+, Browser (Chrome/Firefox/Safari), Next.js, Create React App, Vite

Error Message

TypeError: Cannot read properties of undefined (reading 'map')
#react#undefined#state#props

TL;DR

You called .map() on something that's undefined. Quick fix โ€” add a fallback:

// Crashes
{items.map(item => <li key={item.id}>{item.name}</li>)}

// Safe
{(items ?? []).map(item => <li key={item.id}>{item.name}</li>)}

The Full Error

TypeError: Cannot read properties of undefined (reading 'map')
    at ProductList (ProductList.jsx:12)
    at renderWithHooks (react-dom.development.js:14985)

React renders components synchronously. If products is undefined on the very first render โ€” even for a split second โ€” the .map() call blows up immediately. There's no grace period.

Root Causes

1. State initialized as undefined instead of an empty array

// Wrong โ€” useState() with no argument defaults to undefined
const [products, setProducts] = useState();

// Correct
const [products, setProducts] = useState([]);

2. API data not yet loaded

useEffect runs after the first render. So on that initial paint, your state is still whatever you initialized it to โ€” which might be nothing.

useEffect(() => {
  fetch('/api/products')
    .then(res => res.json())
    .then(data => setProducts(data)); // arrives AFTER first render
}, []);

3. Props named differently in parent and child

// Parent passes 'items'
<ProductList items={products} />

// Child reads 'data' โ€” which is undefined
function ProductList({ data }) {
  return data.map(...); // boom
}

4. API response shape mismatch

// You expect: [{ id: 1, name: 'Widget' }, ...]
// API actually returns: { data: [...], total: 42 }

setProducts(response); // sets products to an object, not an array

This one bites people constantly. Always log the raw response before assuming its shape.

Step-by-Step Fixes

Fix 1: Initialize state as an empty array

The most common culprit. Never let array state start as undefined.

const [products, setProducts] = useState([]); // โœ…

Fix 2: Add a fallback before mapping

Three solid options depending on how defensive you want to be:

// Option A: nullish coalescing (cleanest for undefined/null)
{(products ?? []).map(product => (
  <li key={product.id}>{product.name}</li>
))}

// Option B: optional chaining (renders nothing if undefined)
{products?.map(product => (
  <li key={product.id}>{product.name}</li>
))}

// Option C: Array.isArray (most explicit)
{Array.isArray(products) && products.map(product => (
  <li key={product.id}>{product.name}</li>
))}

Reach for Array.isArray() when the value might be an object, null, or a number โ€” not just undefined. It's the most bulletproof of the three.

Fix 3: Track loading state for async data

function ProductList() {
  const [products, setProducts] = useState([]);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    fetch('/api/products')
      .then(res => res.json())
      .then(data => {
        setProducts(data);
        setLoading(false);
      });
  }, []);

  if (loading) return <p>Loading...</p>;

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

Fix 4: Inspect the API response before using it

fetch('/api/products')
  .then(res => res.json())
  .then(data => {
    console.log(data); // always log before assuming the shape

    // API wraps results as { data: [...], total: 42 }?
    setProducts(data.data ?? data);
  });

Fix 5: Enforce prop types at development time

Catch the wrong prop name before it hits production โ€” not after a confused bug report.

// PropTypes
import PropTypes from 'prop-types';

ProductList.propTypes = {
  items: PropTypes.array.isRequired,
};

ProductList.defaultProps = {
  items: [],
};
// TypeScript โ€” compile-time enforcement, no runtime cost
interface ProductListProps {
  items: Product[];
}

function ProductList({ items }: ProductListProps) {
  return (
    <ul>
      {items.map(item => (
        <li key={item.id}>{item.name}</li>
      ))}
    </ul>
  );
}

Debugging Checklist

  • Open DevTools and check the stack trace โ€” it shows the exact file and line where the crash happened
  • Drop a console.log(yourVariable) just before .map() and see what value it actually holds on the first render
  • Check your useState call โ€” is the initial value undefined or []?
  • Compare the prop name in the parent (where you pass it) against the child (where you destructure it) โ€” one typo is enough
  • Log the raw API response before calling setState to confirm the array is where you expect it

Verifying the Fix

Four things to confirm after applying your fix:

  • Open the browser console โ€” the TypeError should be gone.
  • The list renders correctly with real data.
  • Throttle to Slow 3G in DevTools โ†’ Network tab to stress-test the async path.
  • Test with an empty array response โ€” no crash, just an empty list.

Quick Reference

| Cause                        | Fix                                      |
|------------------------------|------------------------------------------|
| useState() without init      | useState([])                             |
| Async data on first render   | loading state + conditional render       |
| Wrong prop name              | match prop names in parent and child     |
| API returns object, not array| setProducts(response.data ?? response)   |
| Nullable prop from parent    | defaultProps or ?? [] fallback           |

Related Error Notes