DEV Community

Cover image for Building Offline-First React Native Apps
noman akram
noman akram

Posted on

Building Offline-First React Native Apps

Your app works perfectly in the office.
Then your user takes the subway. Opens your app. Sees a spinner.
Nothing happens. They close it. Maybe forever.
The fix is simpler than you think.

Why This Matters

Your users are in elevators, basements, airplanes, rural areas, or just have bad signal.
If your app shows "No Internet Connection" and stops working, you're losing people.

The Simple Solution
You need three things:

  1. Know when offline - Detect network status
  2. Save data locally - Show something useful
  3. Sync when back online - Send queued actions

Let's build it.

Step 1: Detect Network Status

npm install @react-native-community/netinfo
Enter fullscreen mode Exit fullscreen mode

Create a simple hook:

import NetInfo from '@react-native-community/netinfo';
import { useState, useEffect } from 'react';

export const useOnline = () => {
  const [isOnline, setIsOnline] = useState(true);

  useEffect(() => {
    const unsubscribe = NetInfo.addEventListener(state => {
      setIsOnline(state.isConnected);
    });
    return unsubscribe;
  }, []);

  return isOnline;
};
Enter fullscreen mode Exit fullscreen mode

Now you know when to show cached data.

Step 2: Cache API Responses

When your API returns data, save it:

const fetchReports = async () => {
  try {
    const response = await fetch('/api/reports');
    const data = await response.json();

    // Save for offline use
    await AsyncStorage.setItem('reports', JSON.stringify(data));
    return data;

  } catch (error) {
    // Network failed - use cache
    const cached = await AsyncStorage.getItem('reports');
    return cached ? JSON.parse(cached) : [];
  }
};
Enter fullscreen mode Exit fullscreen mode

What happens:

  • Online: Fresh data from API

  • Offline: Last saved data

  • User always sees something

Step 3: Show Users What's Happening

Don't make them guess:

const ReportsScreen = () => {
  const [reports, setReports] = useState([]);
  const isOnline = useOnline();

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

  return (
    <View>
      {!isOnline && (
        <View style={styles.banner}>
          <Text>📴 Offline - Showing saved data</Text>
        </View>
      )}

      <FlatList data={reports} ... />
    </View>
  );
};
Enter fullscreen mode Exit fullscreen mode

Simple banner. Users know what they're seeing.

Step 4: Queue Offline Actions

When users create something offline, save it and sync later:

const submitReport = async (data) => {
  if (!isOnline) {
    // Save locally
    await AsyncStorage.setItem('pending_report', JSON.stringify(data));
    Alert.alert('Saved offline. Will sync when online.');
    return;
  }

  // Normal API call
  await fetch('/api/reports', {
    method: 'POST',
    body: JSON.stringify(data),
  });
};
Enter fullscreen mode Exit fullscreen mode

Step 5: Auto-Sync When Online

Send queued data when connection returns:

useEffect(() => {
  NetInfo.addEventListener(state => {
    if (state.isConnected) {
      syncPendingData(); // Send saved actions
    }
  });
}, []);

const syncPendingData = async () => {
  const pending = await AsyncStorage.getItem('pending_report');

  if (pending) {
    await fetch('/api/reports', {
      method: 'POST',
      body: pending,
    });

    await AsyncStorage.removeItem('pending_report');
  }
};
Enter fullscreen mode Exit fullscreen mode

Real Example: Medical Records App

Imagine you're building a medical records app. Patients need to access their lab reports, but many live in areas with poor connectivity.
Here's how offline support helps:

const MedicalRecordsScreen = () => {
  const [records, setRecords] = useState([]);
  const isOnline = useOnline();

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

  const loadRecords = async () => {
    // Load cache first (instant display)
    const cached = await AsyncStorage.getItem('medical_records');
    if (cached) setRecords(JSON.parse(cached));

    // Then fetch fresh if online
    if (isOnline) {
      try {
        const fresh = await fetch('/api/records').then(r => r.json());
        setRecords(fresh);
        await AsyncStorage.setItem('medical_records', JSON.stringify(fresh));
      } catch (error) {
        console.log('Using cached data');
      }
    }
  };

  return (
    <View>
      {!isOnline && (
        <View style={styles.offlineBanner}>
          <Text>📴 Offline Mode</Text>
        </View>
      )}
      <FlatList 
        data={records}
        renderItem={({ item }) => <RecordCard record={item} />}
      />
    </View>
  );
};
Enter fullscreen mode Exit fullscreen mode

What users get:

  • Instant access to their records (200ms from cache)
  • Works in areas with no signal
  • Fresh data syncs automatically when online
  • No frustrating error screens

This same pattern works for:

  • Shopping apps (cached products)
  • News apps (saved articles)
  • Todo apps (local tasks)
  • Social apps (cached feed)

Quick Checklist

Before shipping:

  • Show offline indicator
  • Cache important data
  • Queue offline actions
  • Auto-sync when online
  • Test in airplane mode

Common Mistakes

Don't do this:

if (!isOnline) {
  throw new Error('No internet');
}
Enter fullscreen mode Exit fullscreen mode

Do this:

if (!isOnline) {
  return getCachedData();
}
Enter fullscreen mode Exit fullscreen mode

When NOT to Cache

Some data shouldn't be cached:

❌ Banking transactions

❌ Live sports scores

❌ Stock prices

❌ Password reset tokens

For these, show "Requires internet connection" and don't fake it.


The Bottom Line

Offline support is three small changes:

  1. Cache responses
  2. Queue actions
  3. Tell users what's happening

Your app doesn't need perfect WiFi.
It needs to work when WiFi isn't perfect.


Try it: Add NetInfo to your app today. Cache one API response. See the difference.

What are you building? Drop a comment.

Top comments (0)