React Native Fetching Data from API

Most apps get their data from a server — product listings, user profiles, news articles, or weather. React Native uses the built-in fetch API to make HTTP requests. This topic covers the full pattern: making requests, showing a loading state, handling errors, and displaying the results.

How API Data Flows Into Your App

React Native App                    Server (API)
────────────────────────────────────────────────────────
1. fetch() sends a request ────────► API receives request
2. Wait... (show spinner)           API queries database
3. Response arrives ◄────────────── API sends JSON back
4. Parse JSON
5. Save to state
6. Render the list

The Basic fetch Pattern

fetch is a built-in JavaScript function. It returns a Promise — a value that will be available later. Use async/await to write asynchronous code that reads like normal sequential code.

import { useEffect, useState } from 'react';
import { View, Text, FlatList, ActivityIndicator } from 'react-native';

export default function PostsScreen() {
  const [posts, setPosts] = useState([]);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    loadPosts();
  }, []);

  async function loadPosts() {
    try {
      const response = await fetch('https://jsonplaceholder.typicode.com/posts');
      const data = await response.json();
      setPosts(data);
    } catch (err) {
      setError('Failed to load posts. Check your connection.');
    } finally {
      setLoading(false);
    }
  }

  if (loading) {
    return (
      <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
        <ActivityIndicator size="large" color="#007AFF" />
        <Text style={{ marginTop: 12 }}>Loading...</Text>
      </View>
    );
  }

  if (error) {
    return (
      <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
        <Text style={{ color: 'red' }}>{error}</Text>
      </View>
    );
  }

  return (
    <FlatList
      data={posts}
      keyExtractor={(item) => String(item.id)}
      renderItem={({ item }) => (
        <View style={{ padding: 16, borderBottomWidth: 1, borderColor: '#eee' }}>
          <Text style={{ fontWeight: 'bold' }}>{item.title}</Text>
          <Text style={{ color: '#666', marginTop: 4 }}>{item.body}</Text>
        </View>
      )}
    />
  );
}
State machine for data fetching:

  App opens
      │
      ▼
  loading = true   → show spinner
      │
      ├── success → loading = false, posts = [...] → show list
      │
      └── failure → loading = false, error = "msg" → show error

Understanding async/await

Without async/await (Promise chain):
fetch(url)
  .then(res => res.json())
  .then(data => setData(data))
  .catch(err => setError(err));

With async/await (cleaner, reads top-to-bottom):
const res  = await fetch(url);
const data = await res.json();
setData(data);

Both do the same thing. async/await is easier to read and debug. Always wrap it in try/catch to handle network failures.

HTTP Methods

fetch supports all HTTP methods. Use the method option to choose.

GET — Read Data (default)

const response = await fetch('https://api.example.com/users');
const users = await response.json();

POST — Create Data

const response = await fetch('https://api.example.com/posts', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ title: 'New Post', body: 'Content here' }),
});
const created = await response.json();

PUT — Update Data

const response = await fetch('https://api.example.com/posts/1', {
  method: 'PUT',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ title: 'Updated Title' }),
});

DELETE — Remove Data

const response = await fetch('https://api.example.com/posts/1', {
  method: 'DELETE',
});

Sending Auth Tokens

Protected APIs require an authentication token in the request header. Pass it through the Authorization header.

const token = 'eyJhbGciOiJIUzI1NiIs...';  // from login response

const response = await fetch('https://api.example.com/profile', {
  headers: {
    'Authorization': `Bearer ${token}`,
    'Content-Type': 'application/json',
  },
});

Checking Response Status

fetch does not throw an error for HTTP error codes like 404 or 500. Check response.ok or response.status yourself.

const response = await fetch('https://api.example.com/data');

if (!response.ok) {
  // response.status = 404, 500, etc.
  throw new Error(`Server error: ${response.status}`);
}

const data = await response.json();
response.ok is true  when status is 200–299
response.ok is false when status is 400–599

Status codes you'll encounter:
200 → OK (success)
201 → Created (POST success)
400 → Bad Request (you sent wrong data)
401 → Unauthorized (bad/missing token)
403 → Forbidden (no permission)
404 → Not Found
500 → Server Error

Pagination — Loading More Items

APIs return data in pages to avoid sending thousands of records at once. Load the next page when the user scrolls to the bottom of the list.

const [posts, setPosts] = useState([]);
const [page, setPage] = useState(1);
const [loadingMore, setLoadingMore] = useState(false);

async function loadMore() {
  if (loadingMore) return;
  setLoadingMore(true);
  const res = await fetch(`https://api.example.com/posts?page=${page}`);
  const newPosts = await res.json();
  setPosts(prev => [...prev, ...newPosts]);   // append new items
  setPage(page + 1);
  setLoadingMore(false);
}

<FlatList
  data={posts}
  keyExtractor={(item) => String(item.id)}
  renderItem={({ item }) => <PostItem {...item} />}
  onEndReached={loadMore}
  onEndReachedThreshold={0.3}    // trigger when 30% from bottom
  ListFooterComponent={loadingMore ? <ActivityIndicator /> : null}
/>
Scroll position diagram:
┌──────────────────────┐
│  Post 1              │
│  Post 2              │
│  Post 3              │
│  Post 4              │
│  Post 5              │← 30% from bottom triggers loadMore
│  Post 6              │
│  Post 7              │
│  (loading spinner)   │← ListFooterComponent
└──────────────────────┘

Using axios Instead of fetch

axios is a popular alternative to fetch that automatically parses JSON, throws errors for non-2xx status codes, and makes request configuration cleaner.

npm install axios

import axios from 'axios';

const { data } = await axios.get('https://api.example.com/posts');
// data is already parsed — no .json() needed
// throws automatically on 4xx/5xx — no need to check response.ok

Summary

Use fetch inside useEffect with async/await to load data when a screen appears. Track loading and error states to show the right UI for every situation. Always check response.ok when using fetch. Use onEndReached on FlatList to implement infinite scroll pagination. For cleaner code, consider axios as an alternative to the raw fetch API.

Leave a Comment