This error occurs when you try to call the .map() method on a variable that is undefined, typically when rendering lists in React components. The issue happens because data hasn't loaded yet, a prop wasn't passed, or an API response is being used before it's fetched.
This error occurs when JavaScript attempts to access the "map" property on a value that is undefined. In React, this almost always happens when rendering arrays conditionally—you're trying to map over data that hasn't been initialized, hasn't loaded yet, or wasn't provided. The error is fundamental: undefined is not an array and has no methods. When React tries to render something like `data.map(item => <div>{item}</div>)` and `data` is undefined, JavaScript throws this TypeError immediately. This is one of the most common React errors because data loading is asynchronous, and components often render before data arrives. The issue is particularly common when: - Fetching data from an API with useEffect - Receiving props that might be undefined - Destructuring objects that don't exist yet - Accessing nested properties on undefined objects - Using state that hasn't been initialized The fix always involves ensuring the data exists and is an array before calling .map() on it.
The simplest fix is to check if the data exists before mapping:
Bad - No check:
function UsersList({ users }) {
return (
<div>
{users.map(user => <p key={user.id}>{user.name}</p>)} // ❌ Error if users is undefined
</div>
);
}Good - Check before mapping:
function UsersList({ users }) {
return (
<div>
{users && users.map(user => <p key={user.id}>{user.name}</p>)}
</div>
);
}Or with optional chaining (ES2020):
function UsersList({ users }) {
return (
<div>
{users?.map(user => <p key={user.id}>{user.name}</p>)}
</div>
);
}Set a default value in function parameters or with PropTypes to ensure an array is always provided:
Using default parameters:
function UsersList({ users = [] }) {
return (
<div>
{users.map(user => <p key={user.id}>{user.name}</p>)}
</div>
);
}Using PropTypes:
import PropTypes from 'prop-types';
function UsersList({ users }) {
return (
<div>
{users.map(user => <p key={user.id}>{user.name}</p>)}
</div>
);
}
UsersList.propTypes = {
users: PropTypes.array.isRequired // Enforces array type
};
UsersList.defaultProps = {
users: [] // Default to empty array
};With TypeScript:
interface UsersListProps {
users: User[]; // Always an array
}
function UsersList({ users = [] }: UsersListProps) {
return (
<div>
{users.map(user => <p key={user.id}>{user.name}</p>)}
</div>
);
}When using useState for data, always initialize with an empty array instead of undefined:
Bad - Initializes to undefined:
function UsersList() {
const [users, setUsers] = useState(); // undefined initially
return (
<div>
{users.map(user => <p key={user.id}>{user.name}</p>)} // ❌ Error
</div>
);
}Good - Initializes to empty array:
function UsersList() {
const [users, setUsers] = useState([]); // Empty array initially
return (
<div>
{users.map(user => <p key={user.id}>{user.name}</p>)} // ✓ Safe
</div>
);
}When fetching data asynchronously, use a loading state to handle the time before data arrives:
Example with useEffect:
function UsersList() {
const [users, setUsers] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
async function fetchUsers() {
try {
const response = await fetch('/api/users');
const data = await response.json();
setUsers(data); // Set array safely
setLoading(false);
} catch (err) {
setError(err.message);
setLoading(false);
}
}
fetchUsers();
}, []);
if (loading) return <p>Loading...</p>;
if (error) return <p>Error: {error}</p>;
return (
<div>
{users.map(user => <p key={user.id}>{user.name}</p>)}
</div>
);
}With React Query (modern approach):
import { useQuery } from '@tanstack/react-query';
function UsersList() {
const { data = [], isLoading, error } = useQuery({
queryKey: ['users'],
queryFn: async () => {
const response = await fetch('/api/users');
return response.json();
}
});
if (isLoading) return <p>Loading...</p>;
if (error) return <p>Error loading users</p>;
return (
<div>
{data.map(user => <p key={user.id}>{user.name}</p>)}
</div>
);
}When accessing nested array properties, ensure all parent objects exist:
Bad - No nested checks:
function PostList({ response }) {
return (
<div>
{response.data.posts.map(post => <p key={post.id}>{post.title}</p>)} // ❌ Error if response, data, or posts is undefined
</div>
);
}Good - With optional chaining:
function PostList({ response }) {
return (
<div>
{response?.data?.posts?.map(post => <p key={post.id}>{post.title}</p>)}
</div>
);
}Or with explicit checks:
function PostList({ response }) {
if (!response || !response.data || !response.data.posts) {
return <p>No posts available</p>;
}
return (
<div>
{response.data.posts.map(post => <p key={post.id}>{post.title}</p>)}
</div>
);
}Check your API documentation and verify the response structure before using it:
function UsersList() {
const [users, setUsers] = useState([]);
useEffect(() => {
fetch('/api/users')
.then(response => response.json())
.then(data => {
// Log the response to verify structure
console.log('API response:', data);
// Check if data is array or nested inside response object
if (Array.isArray(data)) {
setUsers(data);
} else if (Array.isArray(data.users)) {
setUsers(data.users); // Some APIs nest arrays
} else if (data && data.results) {
setUsers(data.results);
} else {
console.error('Unexpected API response structure');
setUsers([]); // Fallback to empty array
}
})
.catch(err => {
console.error('Fetch error:', err);
setUsers([]); // Fallback on error
});
}, []);
return (
<div>
{users.map(user => <p key={user.id}>{user.name}</p>)}
</div>
);
}Always verify with console.log() or browser DevTools to confirm the actual API response before assuming its structure.
TypeScript can catch these errors at compile time instead of runtime:
interface User {
id: number;
name: string;
}
interface UsersListProps {
users: User[]; // Explicitly types as array
}
function UsersList({ users }: UsersListProps) {
// TypeScript guarantees users is User[] array, never undefined
return (
<div>
{users.map(user => (
<p key={user.id}>{user.name}</p>
))}
</div>
);
}
// Or with async data:
function UserListContainer() {
const [users, setUsers] = useState<User[]>([]); // Explicit type
const [loading, setLoading] = useState(true);
useEffect(() => {
fetchUsers()
.then(data => {
setUsers(data); // TypeScript checks data is User[]
setLoading(false);
})
.catch(() => setLoading(false));
}, []);
if (loading) return <p>Loading...</p>;
return <UsersList users={users} />; // Type-safe
}TypeScript will prevent you from passing undefined, null, or wrong types to .map().
Array vs Array-like Objects: The .map() method only exists on true JavaScript arrays. Array-like objects (NodeList, arguments object) won't have .map(). Always convert them to arrays first: Array.from(nodeList).map(...) or [...nodeList].map(...).
Falsy vs Undefined: In React, checking if (data) handles both undefined and null, but also false, 0, and empty strings. Be careful when the value might legitimately be 0 or false. Use explicit checks: if (data !== undefined && data !== null) or with optional chaining: data?.map(...).
Performance with Empty Arrays: Initializing state with an empty array has minimal performance impact. React will render once with the empty array, then again when data loads. If this causes flickering, use the loading state approach (Step 4) to show a loading indicator instead.
React Suspense (Experimental): In React 18+, you can use Suspense for cleaner async data handling, but this is still experimental. It requires a compatible data fetching library like Relay or using frameworks like Next.js.
Error Boundaries: If you want to handle this error globally, wrap components with an Error Boundary:
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error) {
return { hasError: true };
}
componentDidCatch(error, info) {
console.log('Error caught:', error, info);
}
render() {
if (this.state.hasError) {
return <h1>Something went wrong</h1>;
}
return this.props.children;
}
}Custom Hooks for Data Fetching: Extract the loading/error/data logic into a custom hook to avoid repetition across components:
function useFetch<T>(url: string): { data: T[]; loading: boolean; error: Error | null } {
const [data, setData] = useState<T[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<Error | null>(null);
useEffect(() => {
fetch(url)
.then(r => r.json())
.then(setData)
.catch(setError)
.finally(() => setLoading(false));
}, [url]);
return { data, loading, error };
}Debugging Tip: If unsure about data shape, always log the response: console.log('data:', data, 'is array:', Array.isArray(data)). This immediately shows whether the problem is undefined, null, or wrong structure.
Prop spreading could cause security issues
Prop spreading could cause security issues
Error: error:0308010C:digital envelope routines::unsupported
Error: error:0308010C:digital envelope routines::unsupported
React Hook "useEffect" is called conditionally. React Hooks must be called in the exact same order in every component render.
React Hook useEffect placed inside a condition
Hook can only be called inside the body of a function component
Hook can only be called inside the body of a function component
Rollup failed to resolve import during build
How to fix "Rollup failed to resolve import" in React