Skip to content

How to fix: API call in React cannot access response

When a developer states they “cannot access a response” in React, it typically refers to one of three technical failures:

  1. The Promise Result is Undefined: Attempting to access data before the asynchronous operation has completed.
  2. Improper Serialization: Forgetting to parse the raw HTTP response stream into a usable JSON object.
  3. Scope/State Issues: The data exists within the function scope but was not correctly committed to the React State for re-rendering.

This often manifests as the dreaded TypeError: Cannot read properties of undefined or seeing Promise {<pending>} logged in the Console.

Cause Technical Explanation
Missing .json() The Fetch API returns a Response object (a stream), not the data itself. You must await the parsing.
Async Race Condition Attempting to log or use state immediately after calling the setter function (state updates are batch-processed).
CORS Policy The browser blocks the response body due to Cross-Origin Resource Sharing restrictions.
Improper Nesting The API returns an object (e.g., { data: [...] }) but the code treats the response as an array.
Effect Cleanup The component unmounts before the fetch completes, or a memory leak occurs.

1. Correctly Parsing the Response (Fetch API)

Section titled “1. Correctly Parsing the Response (Fetch API)”

Unlike Axios, the native fetch() requires an explicit step to consume the body stream.

// ❌ Incorrect: Accessing the stream directly
const fetchData = async () => {
const response = await fetch('https://api.example.com/data');
console.log(response); // This is a Response object, not your data
};
// ✅ Correct: Consuming the JSON promise
const fetchData = async () => {
try {
const response = await fetch('https://api.example.com/data');
if (!response.ok) throw new Error('Network response was not ok');
const data = await response.json(); // Explicitly parse the body
console.log(data);
} catch (error) {
console.error("Fetch error:", error);
}
};

State updates in React are asynchronous. You cannot access the updated state immediately after calling the setter.

import { useState, useEffect } from 'react';
function DataComponent() {
const [items, setItems] = useState(null);
useEffect(() => {
async function loadData() {
const res = await fetch('https://api.example.com/items');
const json = await res.json();
setItems(json);
// ❌ console.log(items) here will still show 'null' due to closure
}
loadData();
}, []);
// ✅ Access the response here during the next render cycle
if (!items) return <div>Loading...</div>;
return (
<ul>
{items.map(item => <li key={item.id}>{item.name}</li>)}
</ul>
);
}

If your code is correct but the response is empty:

  1. Open Chrome DevTools by pressing F12 or Cmd+Option+I.
  2. Navigate to the Network tab.
  3. Filter by XHR/Fetch.
  4. Click on your request and check the Response sub-tab to verify if the server is actually sending data.
  • Use TypeScript: Define Interfaces for your API responses. This provides IDE autocompletion and prevents “cannot access property” errors at compile time.
    interface UserResponse {
    id: number;
    username: string;
    }
  • Abstraction with Axios: Axios automatically transforms JSON data and throws errors for non-2xx status codes, reducing boilerplate.
  • Data Fetching Libraries: For enterprise applications, use TanStack Query (React Query). It handles caching, loading states, and error boundaries automatically, abstracting the “response access” logic.
  • Defensive Programming: Always check for the existence of data before mapping: data?.items?.map(...).