Making HTTP API Calls with Axios in React Native: A Complete Guide

Aug 5, 2023 â‹… Modified: May 16, 2025 â‹… 5 min read

Bilal Arslan

Introduction

Welcome to our comprehensive tutorial on making HTTP API calls in React Native. In mobile application development, communicating with external servers is essential for fetching data, submitting user information, and much more. Whether you need to retrieve weather forecasts, stock prices, or user profiles, mastering HTTP calls is crucial for creating dynamic, data-driven applications. In this guide, we'll explore how to implement API calls using Axios, a powerful HTTP client for JavaScript applications.

Creating Project

Let's start by setting up our React Native project using Expo CLI:

npx create-expo-app@latest --template blank-typescript react-native-axios

Navigate to the project directory:

cd react-native-axios

Project Setup

Installing Dependencies

We need to install several essential packages for our project:

npm install axios

Starting the Development Environment

Launch your development environment with:

npx expo start

Great! Your emulator is now up and running.

Before building our application, let's explore the fundamental Axios operations you'll need to know. 🚀

Core Operations with Axios in React Native

Axios is a popular promise-based HTTP client that works in both Node.js and browser environments. Since React Native is built on JavaScript, Axios integrates seamlessly with React Native applications. For comprehensive information about Axios, visit the official documentation.

Implementing Axios in your project is straightforward. After importing the library, you can use its methods for different HTTP operations: GET, POST, PUT, and DELETE.

GET Request with Axios

axios.get('/api/example')
    .then(function (response) {
        // When the request succeeds
        console.log(response);
    })
    .catch(function (error) {
        // When the request fails
        console.log(error);
    })
    .then(function () {
        // Always executed regardless of success or failure
    });

POST Request with Axios

axios.post('/api/example', {
        title: title,
        description: description,
    })
    .then(function (response) {
        // When the request succeeds
        console.log(response);
    })
    .catch(function (error) {
        // When the request fails
        console.log(error);
    });

PUT Request with Axios

axios.put('/api/example/1', {
        title: title,
        description: description,
    })
    .then(function (response) {
        // When the request succeeds
        console.log(response);
    })
    .catch(function (error) {
        // When the request fails
        console.log(error);
    });

DELETE Request with Axios

axios.delete('/api/example')
    .then(function (response) {
        // When the request succeeds
        console.log(response);
    })
    .catch(function (error) {
        // When the request fails
        console.log(error);
    });

Implementation

For this tutorial, we'll build a Rick and Morty Character Explorer mobile application. We'll use the Rick and Morty API to fetch character data.

Let's first implement the basic structure of the app, including the loading state. Since data fetching can take time, it's important to show users a loading indicator. We'll use React Native's ActivityIndicator component for this purpose:

App.tsx
import { StatusBar } from 'expo-status-bar';
import { View, Text, TouchableOpacity, FlatList, Image, ActivityIndicator} from 'react-native'
import axios from 'axios';
import React, { useEffect, useState } from 'react'
 
 
export default function App() {
 
  const API_URL = "https://rickandmortyapi.com/"
 
  const [loading, setLoading] = useState<boolean>(true);
 
  useEffect(() => {
    const fetchData = async () => {
    }
    fetchData();
  }, []);
 
 
  if (loading) {
      return(
          <View style={{flex:1, backgroundColor: '#121212', justifyContent:'center', alignItems:'center'}}>
            <ActivityIndicator color="#fff" size="large" />
          </View>
      )
  }
 
  return (
    <View style={{
      flex: 1,
      backgroundColor: '#121212',
      paddingTop: 50,
      paddingHorizontal: 10
    }}>
      <StatusBar style="light" />
    </View>
  );
}
 
Loading screen with activity indicator

In our application, there are several key components working together:

  • useEffect: This hook runs when the component mounts, making the initial API call to fetch the character list. Inside it, we define an asynchronous function that uses Axios to retrieve character data, then updates our state and disables the loading indicator.

  • fetchCharacterDetails: This function is triggered when a user taps on a character. It accepts a character ID, sets a loading state, and makes a targeted API request for that specific character's details. Once the data is received, it updates the state and displays the modal.

  • renderCharacterItem: This function handles the presentation of each character in our list view. It returns a touchable component showing the character's image, name, and status, with a color-coded indicator based on whether they're alive, dead, or unknown.

  • renderCharacterModal: This function creates a detailed view modal that appears when a character is selected. It includes a loading state while fetching details, displays the character's complete information when loaded, and provides a close button for dismissing the modal.

These components work together to create a seamless user experience with proper loading states and error handling throughout the data fetching process.

App.tsx
import { StatusBar } from 'expo-status-bar';
import { View, Text, TouchableOpacity, FlatList, Image, ActivityIndicator, StyleSheet, SafeAreaView, Modal, ScrollView, Dimensions } from 'react-native';
import axios from 'axios';
import React, { useEffect, useState } from 'react';
 
/**
 * Character interface defines the structure of character data
 * from the Rick and Morty API
 */
interface Character {
  id: number;
  name: string;
  status: string;
  species: string;
  type: string;
  gender: string;
  image: string;
  episode: string[];
  created: string;
  location: {
    name: string;
    url: string;
  };
  origin: {
    name: string;
    url: string;
  };
}
 
/**
 * API response interface for the character listing endpoint
 */
interface ApiResponse {
  info: {
    count: number;
    pages: number;
    next: string | null;
    prev: string | null;
  };
  results: Character[];
}
 
export default function App() {
  // Base URL for the Rick and Morty API
  const API_URL = "https://rickandmortyapi.com/api/";
 
  // Application state management
  const [loading, setLoading] = useState<boolean>(true);                                  // Loading state for initial data fetch
  const [characters, setCharacters] = useState<Character[]>([]);                                    // Array of characters from API
  const [selectedCharacter, setSelectedCharacter] = useState<Character | null>(null);     // Currently selected character
  const [modalVisible, setModalVisible] = useState<boolean>(false);                       // Controls modal visibility
  const [detailLoading, setDetailLoading] = useState<boolean>(false);                     // Loading state for character details
 
  /**
   * useEffect hook to fetch the initial list of characters when the component mounts
   * This is called only once when the app starts
   */
  useEffect(() => {
    const fetchData = async () => {
      try {
        // Initial API call to get characters
        const response = await axios.get<ApiResponse>(`${API_URL}character`);
 
        // Update state with received characters
        setCharacters(response.data.results);
        setLoading(false);
      } catch (error) {
        console.error("Error fetching data:", error);
        setLoading(false);
      }
    };
    fetchData();
  }, []);
 
  /**
   * fetchCharacterDetails - Fetches detailed information about a specific character
   *
   * This function:
   * 1. Sets loading state for detail view
   * 2. Makes API request to get specific character data by ID
   * 3. Updates the selectedCharacter state with complete data
   * 4. Shows the modal with character details
   *
   * @param characterId - The ID of the character to fetch details for
   */
  const fetchCharacterDetails = async (characterId: number) => {
    setDetailLoading(true);
    try {
      // Make API request to get specific character details
      const response = await axios.get<Character>(`${API_URL}character/${characterId}`);
 
      // Update state with the detailed character data
      setSelectedCharacter(response.data);
      setModalVisible(true);
    } catch (error) {
      console.error("Error fetching character details:", error);
    } finally {
      setDetailLoading(false);
    }
  };
 
  /**
   * renderCharacterItem - Renders each character card in the FlatList
   *
   * This component:
   * 1. Displays character image
   * 2. Shows basic character info
   * 3. Handles tap interaction to open character details
   *
   * @param item - The character data to render
   */
  const renderCharacterItem = ({ item }: { item: Character }) => (
    <TouchableOpacity
      style={styles.characterCard}
      onPress={() => fetchCharacterDetails(item.id)}
    >
      <Image
        source={{ uri: item.image }}
        style={styles.characterImage}
      />
      <View style={styles.characterInfo}>
        <Text style={styles.characterName}>{item.name}</Text>
        <View style={styles.characterStatus}>
          <View style={[
            styles.statusDot,
            {
              backgroundColor:
                item.status === 'Alive' ? '#55CC44' :
                item.status === 'Dead' ? '#D63D2E' : '#9E9E9E'
            }
          ]} />
          <Text style={styles.characterStatusText}>{item.status} - {item.species}</Text>
        </View>
      </View>
    </TouchableOpacity>
  );
 
  /**
   * renderCharacterModal - Renders the modal with detailed character information
   *
   * This component:
   * 1. Shows a loading indicator while fetching character details
   * 2. Displays detailed character information once loaded
   * 3. Includes a close button to dismiss the modal
   *
   * The data shown includes:
   * - Character image
   * - Name, status, and species with visual status indicator
   * - Origin and current location
   * - Type (if available)
   * - Gender
   * - Creation date
   * - Number of episodes the character appears in
   */
  const renderCharacterModal = () => {
    if (!selectedCharacter) return null;
 
    return (
      <Modal
        animationType="slide"
        transparent={true}
        visible={modalVisible}
        onRequestClose={() => setModalVisible(false)}
      >
        <View style={styles.modalOverlay}>
          <View style={styles.modalContent}>
            {detailLoading ? (
              // Show loading indicator while fetching character details
              <View style={styles.modalLoading}>
                <ActivityIndicator color="#62CF9B" size="large" />
                <Text style={styles.modalLoadingText}>Loading character details...</Text>
              </View>
            ) : (
              // Display character details once loaded
              <ScrollView>
                <Image
                  source={{ uri: selectedCharacter.image }}
                  style={styles.modalImage}
                  resizeMode="cover"
                />
                <View style={styles.modalInfo}>
                  <Text style={styles.modalName}>{selectedCharacter.name}</Text>
                  <View style={styles.characterStatus}>
                    <View style={[
                      styles.statusDot,
                      {
                        backgroundColor:
                          selectedCharacter.status === 'Alive' ? '#55CC44' :
                          selectedCharacter.status === 'Dead' ? '#D63D2E' : '#9E9E9E'
                      }
                    ]} />
                    <Text style={styles.modalStatusText}>
                      {selectedCharacter.status} - {selectedCharacter.species}
                    </Text>
                  </View>
 
                  <View style={styles.modalDetailSection}>
                    <Text style={styles.modalDetailTitle}>Origin</Text>
                    <Text style={styles.modalDetailText}>{selectedCharacter.origin.name}</Text>
                  </View>
 
                  <View style={styles.modalDetailSection}>
                    <Text style={styles.modalDetailTitle}>Last known location</Text>
                    <Text style={styles.modalDetailText}>{selectedCharacter.location.name}</Text>
                  </View>
 
                  {selectedCharacter.type ? (
                    <View style={styles.modalDetailSection}>
                      <Text style={styles.modalDetailTitle}>Type</Text>
                      <Text style={styles.modalDetailText}>{selectedCharacter.type}</Text>
                    </View>
                  ) : null}
 
                  <View style={styles.modalDetailSection}>
                    <Text style={styles.modalDetailTitle}>Gender</Text>
                    <Text style={styles.modalDetailText}>{selectedCharacter.gender}</Text>
                  </View>
 
                  <View style={styles.modalDetailSection}>
                    <Text style={styles.modalDetailTitle}>Created</Text>
                    <Text style={styles.modalDetailText}>
                      {new Date(selectedCharacter.created).toLocaleDateString()}
                    </Text>
                  </View>
 
                  <View style={styles.modalDetailSection}>
                    <Text style={styles.modalDetailTitle}>Appears in</Text>
                    <Text style={styles.modalDetailText}>
                      {selectedCharacter.episode.length} episodes
                    </Text>
                  </View>
                </View>
              </ScrollView>
            )}
 
            <TouchableOpacity
              style={styles.closeButton}
              onPress={() => setModalVisible(false)}
            >
              <Text style={styles.closeButtonText}>Close</Text>
            </TouchableOpacity>
          </View>
        </View>
      </Modal>
    );
  };
 
  /**
   * Loading state view - shown when initially loading the character list
   */
  if (loading && characters.length === 0) {
    return (
      <View style={styles.loadingContainer}>
        <ActivityIndicator color="#fff" size="large" />
      </View>
    );
  }
 
  /**
   * Main application view
   * - Displays the header with app title
   * - Shows the list of characters using FlatList
   * - Renders the character detail modal when a character is selected
   */
  return (
    <SafeAreaView style={styles.container}>
      <StatusBar style="light" />
      <View style={styles.header}>
        <Text style={styles.headerTitle}>Rick and Morty</Text>
        <Text style={styles.headerSubtitle}>Character Explorer</Text>
      </View>
      <FlatList
        data={characters}
        keyExtractor={(item) => item.id.toString()}
        renderItem={renderCharacterItem}
        contentContainerStyle={styles.listContainer}
      />
      {renderCharacterModal()}
    </SafeAreaView>
  );
}
 
/**
 * Styles for the application
 * Using a dark theme with green accent colors to match Rick and Morty's aesthetic
 */
const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#121212',
  },
  loadingContainer: {
    flex: 1,
    backgroundColor: '#121212',
    justifyContent: 'center',
    alignItems: 'center',
  },
  header: {
    paddingTop: 15,
    paddingBottom: 15,
    paddingHorizontal: 16,
    backgroundColor: '#1E1E1E',
  },
  headerTitle: {
    fontSize: 28,
    fontWeight: 'bold',
    color: '#62CF9B',
    textAlign: 'center',
  },
  headerSubtitle: {
    fontSize: 16,
    color: '#A7A7A7',
    textAlign: 'center',
    marginTop: 4,
  },
  listContainer: {
    padding: 12,
  },
  characterCard: {
    backgroundColor: '#1E1E1E',
    borderRadius: 12,
    marginBottom: 16,
    overflow: 'hidden',
    flexDirection: 'row',
    shadowColor: "#000",
    shadowOffset: {
      width: 0,
      height: 2,
    },
    shadowOpacity: 0.25,
    shadowRadius: 3.84,
    elevation: 5,
  },
  characterImage: {
    width: 140,
    height: 140,
  },
  characterInfo: {
    flex: 1,
    padding: 12,
  },
  characterName: {
    fontSize: 18,
    fontWeight: 'bold',
    color: '#FFFFFF',
    marginBottom: 6,
  },
  characterStatus: {
    flexDirection: 'row',
    alignItems: 'center',
    marginBottom: 10,
  },
  statusDot: {
    width: 9,
    height: 9,
    borderRadius: 4.5,
    marginRight: 6,
  },
  characterStatusText: {
    color: '#CCCCCC',
    fontSize: 14,
  },
  infoLabel: {
    color: '#888888',
    fontSize: 12,
    marginTop: 5,
  },
  infoText: {
    color: '#FFFFFF',
    fontSize: 14,
  },
  footerLoader: {
    marginVertical: 16,
  },
  modalOverlay: {
    flex: 1,
    backgroundColor: 'rgba(0, 0, 0, 0.7)',
    justifyContent: 'center',
    alignItems: 'center',
  },
  modalContent: {
    width: Dimensions.get('window').width * 0.9,
    maxHeight: Dimensions.get('window').height * 0.8,
    backgroundColor: '#1E1E1E',
    borderRadius: 16,
    overflow: 'hidden',
  },
  modalImage: {
    width: '100%',
    height: 200,
  },
  modalInfo: {
    padding: 20,
  },
  modalName: {
    fontSize: 24,
    fontWeight: 'bold',
    color: '#FFFFFF',
    marginBottom: 8,
  },
  modalStatusText: {
    color: '#FFFFFF',
    fontSize: 16,
  },
  modalDetailSection: {
    marginTop: 16,
  },
  modalDetailTitle: {
    fontSize: 14,
    color: '#62CF9B',
    fontWeight: '600',
    marginBottom: 4,
  },
  modalDetailText: {
    fontSize: 18,
    color: '#FFFFFF',
  },
  closeButton: {
    backgroundColor: '#62CF9B',
    padding: 15,
    alignItems: 'center',
    marginTop: 10,
  },
  closeButtonText: {
    color: '#000000',
    fontSize: 16,
    fontWeight: 'bold',
  },
  modalLoading: {
    padding: 40,
    alignItems: 'center',
    justifyContent: 'center',
  },
  modalLoadingText: {
    color: '#FFFFFF',
    marginTop: 10,
    fontSize: 16,
  },
});
 
Character details modal showing data fetched with Axios
Character details modal showing data fetched with Axios

Our application now successfully fetches and displays character data from the Rick and Morty API. When a user selects a character from the list, we make a second API call to retrieve the detailed information and display it in a modal.

Source Code

For the complete project files, visit our GitHub Repository.

Project Dependencies

  "dependencies": {
    "axios": "^1.9.0",
    "expo": "~53.0.9",
    "expo-status-bar": "~2.2.3",
    "react": "19.0.0",
    "react-native": "0.79.2",
    "react-native-reanimated": "~3.17.4"
  },
  "devDependencies": {
    "@babel/core": "^7.25.2",
    "@types/react": "~19.0.10",
    "typescript": "~5.8.3"
  },

Conclusion

In this tutorial, we've successfully implemented HTTP API calls in a React Native application using Axios. We've covered:

  • Setting up a React Native project with Expo
  • Installing and configuring Axios
  • Making GET requests to fetch data from an external API
  • Handling loading states for better user experience
  • Displaying the fetched data in a visually appealing UI
  • Implementing modals for detailed views with additional API calls

Axios provides a clean, promise-based approach to making HTTP requests in React Native applications. Its intuitive API and robust error handling make it an excellent choice for connecting your mobile apps to external data sources.

For your next steps, consider implementing pagination to load more characters, adding search functionality, or exploring other API endpoints to enhance the application further. You might also want to implement caching strategies to improve performance and reduce unnecessary network requests.

Remember that proper error handling and loading states are crucial for providing a smooth user experience, especially when dealing with network operations that might fail or take time to complete.

By mastering Axios and HTTP communication in React Native, you've added a valuable skill to your mobile development toolkit that will serve you well in building data-driven applications.