How To Fix The “React Map Is Not A Function” Error

I recently hit one of those frustrating bugs that feels obvious in hindsight but had me scratching my head at first. While building a small project with Spring Boot, MySQL, and React, I wanted to display a user’s expenses in a table. Everything compiled fine, but as soon as I opened the page.

Uncaught TypeError: this.state.Expenses.map is not a function

Sound familiar, Let’s walk through what happened, why it happens, and how I fixed it. Then I’ll share how I leveled up the project with some extra functionality like search, sort, totals, loading states, and delete.

The Code I Started With

Here’s what my original setup looked like.

React (Class Component)

componentDidMount(){
    const currentUser = AuthService.getCurrentUser();
    let userId = {...this.state.userId};
    userId = currentUser.id;
    this.setState({userId});

    UserService.getExpense(userId).then((res) =>{
        this.setState({Expenses: res.data});
    });
}
</thead>
<tbody>
    {
        this.state.Expenses.map(
            (expense, key) => {
                return (
                    <tr key = {key}>
                        <td> {expense.title} </td>   
                        <td><Moment date={expense.date} format="YYYY/MM/DD"/></td>
                        <td> {expense.category}</td>
                        <td> {expense.amount}</td>
                        <td>
                            <button onClick={ () => this.deleteExpense(expense.id)} className="btn btn-danger">Delete </button>
                        </td>
                    </tr>
                )
            }
        )
    }
</tbody>
</table>

Axios Function

const API_URL_EX = 'http://localhost:8080/api/test/expense';

getExpense(userId){
  return axios.get(API_URL_EX + '/' + userId);
}

Spring Boot Controller

@GetMapping("/expense/{userId}")
public ResponseEntity<?> getExpense(@PathVariable Long userId){
    Optional<Expense> expense = expenseRepository.findByUserId(userId);
    return expense.map(response -> ResponseEntity.ok().body(response))
            .orElse(new ResponseEntity<>(HttpStatus.NOT_FOUND));
}

Sample Database Row

# id, amount, category, date, title, user_id
'1', '50000', 'Shopping', '2022-11-06 11:21:23', 'Dubai Trip', '1'

And then the error:

viewexpense.component.js:68 Uncaught TypeError: this.state.Expenses.map is not a function

Why that Error Occur

The key is in the phrase:

.map only exists on arrays.

React was trying to loop over this.state.Expenses as if it were an array. But my backend controller wasn’t sending an array. I used Optional<Expense> and findByUserId, which only returns a single expense object. That means React got an object like:

{"id":1,"title":"Dubai Trip","date":"2022-11-06T11:21:23","category":"Shopping","amount":50000}

Not an array. So when React ran this.state.Expenses.map(...), it crashed.

Two Correct Ways to Fix It

(Recommended): Return a List From the Backend

If a user can have multiple expenses, it makes sense to always return a list.

Repository

public interface ExpenseRepository extends JpaRepository<Expense, Long> {
    List<Expense> findAllByUserId(Long userId);
}

Controller

@GetMapping("/expense/{userId}")
public ResponseEntity<List<Expense>> getExpenses(@PathVariable Long userId){
    List<Expense> expenses = expenseRepository.findAllByUserId(userId);
    return ResponseEntity.ok(expenses); // returns a JSON array
}

Now React receives res.data as an array, and .map works.

Wrap the Single Object in an Array on the Client

If changing the backend isn’t an option, I can adapt the frontend:

UserService.getExpense(userId).then((res) => {
  const data = res.data;
  const Expenses = Array.isArray(data) ? data : (data ? [data] : []);
  this.setState({ Expenses });
});

And I also initialize state as an array:

state = { Expenses: [] };

Both options fix the error, but Option A is cleaner.

Practice Upgrade: a Modern React Version

Once I solved the error, I decided to take the chance to refactor into a functional component with hooks. I added:

API Service

// services/expenseService.js
import axios from "axios";
const API_URL_EX = "http://localhost:8080/api/test/expense";

export const getExpenses = (userId) => axios.get(`${API_URL_EX}/${userId}`);
export const deleteExpense = (id) => axios.delete(`${API_URL_EX}/item/${id}`);

Spring Boot Delete Endpoint

@DeleteMapping("/expense/item/{id}")
public ResponseEntity<Void> deleteExpense(@PathVariable Long id) {
    if (!expenseRepository.existsById(id)) {
        return ResponseEntity.notFound().build();
    }
    expenseRepository.deleteById(id);
    return ResponseEntity.noContent().build();
}

React Component

import React, { useEffect, useMemo, useState } from "react";
import Moment from "react-moment";
import { getExpenses, deleteExpense } from "./services/expenseService";
import AuthService from "./services/authService";

export default function ExpenseTable() {
  const [expenses, setExpenses] = useState([]);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState("");
  const [query, setQuery] = useState("");
  const [sortKey, setSortKey] = useState("date");
  const [sortDir, setSortDir] = useState("desc");

  useEffect(() => {
    const { id } = AuthService.getCurrentUser();
    getExpenses(id)
      .then((res) => setExpenses(Array.isArray(res.data) ? res.data : []))
      .catch((e) => setError("Failed to load expenses"))
      .finally(() => setLoading(false));
  }, []);

  const filtered = useMemo(() => {
    const q = query.trim().toLowerCase();
    const list = q
      ? expenses.filter((e) =>

Guardrail for The Future

  • Always initialize arrays as arrays: const [items, setItems] = useState([]);
  • Validate API shapes before using .map.
  • Render defensively with Array.isArray.
  • Match API contract with UI: if the UI expects a list, make sure the backend returns one.

Final Thought

In my case, the error boiled down to this: Expenses was not an array. The cleanest fix was making the backend return a list of expenses. But if that’s not possible, you can always wrap the object into an array on the client. Once that was solved, I enhanced the project with features that made it feel like a real-world app search, sort, totals, delete, and proper loading states.

Related blog posts