Skip to main content
Most mobile apps need to load data from remote servers. React Native uses the same fetch API that’s available in web browsers, making it familiar if you’ve done web development.

The Fetch API

React Native includes the Fetch API for making HTTP requests. From the React Native source (Libraries/Network/fetch.js):
fetch.js (from source)
// side-effectful require() to put fetch,
// Headers, Request, Response in global scope
require('whatwg-fetch');

export const fetch = global.fetch;
export const Headers = global.Headers;
export const Request = global.Request;
export const Response = global.Response;

Basic GET Request

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

function UserProfile({userId}) {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    fetch(`https://api.example.com/users/${userId}`)
      .then(response => response.json())
      .then(data => {
        setUser(data);
        setLoading(false);
      })
      .catch(error => {
        setError(error.message);
        setLoading(false);
      });
  }, [userId]);

  if (loading) {
    return <ActivityIndicator size="large" />;
  }

  if (error) {
    return <Text>Error: {error}</Text>;
  }

  return (
    <View>
      <Text>Name: {user.name}</Text>
      <Text>Email: {user.email}</Text>
    </View>
  );
}

Using Async/Await

Modern JavaScript async/await syntax makes network code cleaner:
import {useEffect, useState} from 'react';
import {View, Text, FlatList} from 'react-native';

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

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

  const loadPosts = async () => {
    try {
      const response = await fetch('https://api.example.com/posts');
      
      if (!response.ok) {
        throw new Error(`HTTP error! status: ${response.status}`);
      }
      
      const data = await response.json();
      setPosts(data);
    } catch (error) {
      setError(error.message);
    } finally {
      setLoading(false);
    }
  };

  if (loading) return <Text>Loading...</Text>;
  if (error) return <Text>Error: {error}</Text>;

  return (
    <FlatList
      data={posts}
      renderItem={({item}) => (
        <View>
          <Text>{item.title}</Text>
        </View>
      )}
      keyExtractor={item => item.id.toString()}
    />
  );
}

POST Requests

Sending data to a server:
import {useState} from 'react';
import {View, TextInput, Button, Alert} from 'react-native';

function CreatePost() {
  const [title, setTitle] = useState('');
  const [body, setBody] = useState('');
  const [submitting, setSubmitting] = useState(false);

  const handleSubmit = async () => {
    setSubmitting(true);
    
    try {
      const response = await fetch('https://api.example.com/posts', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({
          title: title,
          body: body,
        }),
      });

      if (!response.ok) {
        throw new Error('Failed to create post');
      }

      const data = await response.json();
      Alert.alert('Success', 'Post created successfully!');
      
      // Reset form
      setTitle('');
      setBody('');
    } catch (error) {
      Alert.alert('Error', error.message);
    } finally {
      setSubmitting(false);
    }
  };

  return (
    <View>
      <TextInput
        placeholder="Title"
        value={title}
        onChangeText={setTitle}
      />
      <TextInput
        placeholder="Body"
        value={body}
        onChangeText={setBody}
        multiline
      />
      <Button
        title="Submit"
        onPress={handleSubmit}
        disabled={submitting}
      />
    </View>
  );
}

HTTP Methods

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

Authentication

Bearer Token Authentication

const TOKEN = 'your-auth-token';

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

Complete Auth Example

import {useState, useEffect} from 'react';
import AsyncStorage from '@react-native-async-storage/async-storage';

function useAuth() {
  const [token, setToken] = useState(null);

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

  const loadToken = async () => {
    const savedToken = await AsyncStorage.getItem('authToken');
    setToken(savedToken);
  };

  const login = async (email, password) => {
    const response = await fetch('https://api.example.com/login', {
      method: 'POST',
      headers: {'Content-Type': 'application/json'},
      body: JSON.stringify({email, password}),
    });

    const data = await response.json();
    
    if (response.ok) {
      await AsyncStorage.setItem('authToken', data.token);
      setToken(data.token);
      return {success: true};
    }
    
    return {success: false, error: data.message};
  };

  const logout = async () => {
    await AsyncStorage.removeItem('authToken');
    setToken(null);
  };

  const fetchWithAuth = async (url, options = {}) => {
    return fetch(url, {
      ...options,
      headers: {
        ...options.headers,
        'Authorization': `Bearer ${token}`,
        'Content-Type': 'application/json',
      },
    });
  };

  return {token, login, logout, fetchWithAuth};
}

Error Handling

const fetchData = async () => {
  try {
    const response = await fetch('https://api.example.com/data');
    
    // Check HTTP status
    if (!response.ok) {
      switch (response.status) {
        case 400:
          throw new Error('Bad request');
        case 401:
          throw new Error('Unauthorized');
        case 404:
          throw new Error('Not found');
        case 500:
          throw new Error('Server error');
        default:
          throw new Error(`HTTP error! status: ${response.status}`);
      }
    }
    
    const data = await response.json();
    return data;
    
  } catch (error) {
    if (error.message === 'Network request failed') {
      // No internet connection
      Alert.alert('Network Error', 'Please check your internet connection');
    } else {
      // Other errors
      Alert.alert('Error', error.message);
    }
    throw error;
  }
};

Request Timeout

const fetchWithTimeout = async (url, options = {}, timeout = 5000) => {
  const controller = new AbortController();
  const id = setTimeout(() => controller.abort(), timeout);
  
  try {
    const response = await fetch(url, {
      ...options,
      signal: controller.signal,
    });
    clearTimeout(id);
    return response;
  } catch (error) {
    clearTimeout(id);
    if (error.name === 'AbortError') {
      throw new Error('Request timeout');
    }
    throw error;
  }
};

Real-World Example: News Feed

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

function NewsFeed() {
  const [articles, setArticles] = useState([]);
  const [loading, setLoading] = useState(true);
  const [refreshing, setRefreshing] = useState(false);
  const [page, setPage] = useState(1);
  const [hasMore, setHasMore] = useState(true);

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

  const loadArticles = async (isRefresh = false) => {
    if (!hasMore && !isRefresh) return;

    try {
      const currentPage = isRefresh ? 1 : page;
      const response = await fetch(
        `https://api.example.com/articles?page=${currentPage}&limit=10`
      );
      
      const data = await response.json();
      
      if (isRefresh) {
        setArticles(data.articles);
        setPage(2);
      } else {
        setArticles([...articles, ...data.articles]);
        setPage(page + 1);
      }
      
      setHasMore(data.hasMore);
    } catch (error) {
      console.error('Error loading articles:', error);
    } finally {
      setLoading(false);
      setRefreshing(false);
    }
  };

  const onRefresh = () => {
    setRefreshing(true);
    setHasMore(true);
    loadArticles(true);
  };

  const renderArticle = ({item}) => (
    <View style={styles.article}>
      <Text style={styles.title}>{item.title}</Text>
      <Text style={styles.summary}>{item.summary}</Text>
      <Text style={styles.date}>{item.publishedAt}</Text>
    </View>
  );

  if (loading) {
    return (
      <View style={styles.center}>
        <ActivityIndicator size="large" />
      </View>
    );
  }

  return (
    <FlatList
      data={articles}
      renderItem={renderArticle}
      keyExtractor={item => item.id}
      refreshControl={
        <RefreshControl refreshing={refreshing} onRefresh={onRefresh} />
      }
      onEndReached={() => loadArticles()}
      onEndReachedThreshold={0.5}
      ListFooterComponent={
        hasMore ? <ActivityIndicator style={styles.footer} /> : null
      }
    />
  );
}

const styles = StyleSheet.create({
  center: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
  },
  article: {
    padding: 16,
    borderBottomWidth: 1,
    borderBottomColor: '#e0e0e0',
  },
  title: {
    fontSize: 18,
    fontWeight: 'bold',
    marginBottom: 8,
  },
  summary: {
    fontSize: 14,
    color: '#666',
    marginBottom: 8,
  },
  date: {
    fontSize: 12,
    color: '#999',
  },
  footer: {
    marginVertical: 20,
  },
});

export default NewsFeed;

WebSocket Support

For real-time communication:
import {useEffect, useState} from 'react';
import {View, Text, FlatList} from 'react-native';

function ChatRoom({roomId}) {
  const [messages, setMessages] = useState([]);
  const [ws, setWs] = useState(null);

  useEffect(() => {
    const websocket = new WebSocket('wss://api.example.com/chat');

    websocket.onopen = () => {
      console.log('Connected to WebSocket');
      websocket.send(JSON.stringify({type: 'join', roomId}));
    };

    websocket.onmessage = (event) => {
      const message = JSON.parse(event.data);
      setMessages(prev => [...prev, message]);
    };

    websocket.onerror = (error) => {
      console.error('WebSocket error:', error);
    };

    websocket.onclose = () => {
      console.log('WebSocket disconnected');
    };

    setWs(websocket);

    return () => {
      websocket.close();
    };
  }, [roomId]);

  const sendMessage = (text) => {
    if (ws && ws.readyState === WebSocket.OPEN) {
      ws.send(JSON.stringify({type: 'message', text}));
    }
  };

  return (
    <View>
      <FlatList
        data={messages}
        renderItem={({item}) => <Text>{item.text}</Text>}
        keyExtractor={(item, index) => index.toString()}
      />
    </View>
  );
}

Best Practices

Network requests can fail for many reasons. Always wrap fetch calls in try-catch blocks.
Provide feedback to users while data is loading with ActivityIndicator or skeleton screens.
Check for network errors and show appropriate messages to users.
Cleaner than promises for sequential operations and error handling.
Prevent requests from hanging indefinitely.
Use AsyncStorage or libraries like React Query to cache responses.

Next Steps

Navigation

Learn to navigate between screens in your app

Using Lists

Display network data in efficient lists

Build docs developers (and LLMs) love