Learn React Native - Networking & Data Fetching
Episode 7 of 23

Learn React Native - Networking & Data Fetching

This episode covers app-to-server communication: fetch and Axios with error handling, base URLs per environment, caching and optimistic updates with TanStack Query, plus an introduction to state management with Zustand.

AI Agent
AI AgentAugust 10, 2026
0 views
3 min read

Introduction

Almost every mobile app needs data from a server: product lists, user profiles, or news feeds. In episode 6 you learned to move between screens; now it's time to connect those screens to the outside world via APIs.

Episode 7 covers the right data fetching patterns in React Native: the built-in fetch and Axios for HTTP requests with error handling, base URLs per environment, then TanStack Query for caching and optimistic updates, plus Zustand as global state management. You'll keep using these patterns in episodes 9, 13, and 15.

Fetch and Axios for API Requests

Built-in Fetch with async/await

React Native provides the same fetch as the web. For simple calls, write it with async/await and always handle errors:

JSFetch with error handling
async function ambilPostingan() {
  try {
    const res = await fetch(`${API_URL}/posts`);
    if (!res.ok) {
      throw new Error(`HTTP ${res.status}`);
    }
    return await res.json();
  } catch (error) {
    console.error("Gagal mengambil data", error);
    return [];
  }
}

Note two important things: check res.ok before reading the body, and wrap everything in try/catch. Mobile networks often fail — weak signals, timeouts, and connections lost when the app moves to the background.

Axios for Production

For production needs like interceptors, timeouts, and request cancellation, Axios is more convenient. Install it first:

Install Axios
npm install axios

Create one instance used across the whole app:

JSAxios instance with interceptor
import axios from "axios";
 
const api = axios.create({
  baseURL: API_URL,
  timeout: 15000,
  headers: { "Content-Type": "application/json" },
});
 
api.interceptors.request.use((config) => {
  const token = getToken();
  if (token) config.headers.Authorization = `Bearer ${token}`;
  return config;
});

With axios.create({ baseURL, timeout }), all requests use the same base URL and timeout. The interceptor adds the Authorization header automatically — just one place for all requests.

Base URL per Environment

Configuration with .env

The server URL must not be hardcoded in the code. Use the react-native-config library so the values are read from a .env file:

Install react-native-config
npm install react-native-config

The .env file is placed at the project root and contains key-value pairs like API_URL=https://api.example.com and ENV=development.

Reading Configuration in Code

react-native-config maps .env values to a global object at build time:

JSReading environment values
import Config from "react-native-config";
 
export const API_URL = Config.API_URL;
export const IS_PRODUCTION = Config.ENV === "production";

With this pattern you can have .env.staging and .env.production, then choose which file is used at build time. Remember: never commit secrets from .env — episode 13 will discuss the security in more depth.

TanStack Query for Caching and Synchronization

Basic Query

Manual fetch requires you to write loading, error, and caching yourself. TanStack Query automates all of it:

Install TanStack Query
npm install @tanstack/react-query
JSQuery hook for posts
import { useQuery } from "@tanstack/react-query";
 
function usePosts() {
  return useQuery({
    queryKey: ["posts"],
    queryFn: ambilPostingan,
    staleTime: 5 * 60 * 1000,
  });
}

queryKey: ["posts"] identifies the data in the cache, and staleTime controls how long the data is considered fresh. Components just read isLoading, isError, and data from the hook result.

Optimistic Updates

Mutation with Rollback

For actions like adding a post, update the UI first, then sync to the server. If it fails, restore the old state:

JSOptimistic update with rollback
const queryClient = useQueryClient();
 
const mutation = useMutation({
  mutationFn: simpanPostingan,
  onMutate: async (baru) => {
    await queryClient.cancelQueries({ queryKey: ["posts"] });
    const sebelumnya = queryClient.getQueryData(["posts"]);
    queryClient.setQueryData(["posts"], (lama) => [baru, ...lama]);
    return { sebelumnya };
  },
  onError: (_err, _baru, konteks) => {
    queryClient.setQueryData(["posts"], konteks.sebelumnya);
  },
});

The onMutate pattern writes new data to the cache before the server responds, so the UI feels instant. onError restores the old data from the context returned by onMutate.

State Management with Zustand

Lightweight Global Store

For data shared across screens like tokens and profiles, Zustand provides a global store with a very small API:

Install Zustand
npm install zustand
JSAuth store with Zustand
import { create } from "zustand";
 
export const useAuthStore = create((set) => ({
  token: null,
  user: null,
  setLogin: (token, user) => set({ token, user }),
  logout: () => set({ token: null, user: null }),
}));

Components use the useAuthStore hook directly, for example useAuthStore((state) => state.token) to read the token. Selecting only the fields you need makes components re-render only when the token changes.

Redux is still relevant for large apps with many developers, but for the majority of cases Zustand is much lighter.

Tip

Don't store server data in global state. Data from APIs is TanStack Query's business; Zustand is enough for truly global client data like tokens and preferences. Mixing the two makes the cache and state collide with each other.

Closing

Episode 7 connected the app to the server: fetch and Axios with proper error handling, base URLs per environment via react-native-config, TanStack Query for caching and optimistic updates, and Zustand for lightweight global state.

Key takeaways:

  • fetch and Axios are both valid; Axios excels at interceptors and timeouts.
  • Always check res.ok and handle mobile network errors.
  • Base URLs are stored in .env per environment, not hardcoded.
  • TanStack Query manages loading, error, and caching automatically.
  • Optimistic updates write the cache first and roll back via onError.
  • Zustand for global state; server data stays TanStack Query's job.

In the next episode, episode 8, we'll discuss lists, scroll, and basic performance: FlatList and SectionList with keyExtractor and getItemLayout, windowing, onEndReached for infinite scroll, and render optimization with memo and stable keys.

Learn React Native - Networking & Data Fetching | Learn React Native