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
useStatecall โ is the initial valueundefinedor[]? - 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
setStateto confirm the array is where you expect it
Verifying the Fix
Four things to confirm after applying your fix:
- Open the browser console โ the
TypeErrorshould 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 |

