Flex App
FLEX APP
Pricing
Back to blogs
Build Offline-First Expo Apps: Caching, Storage & Syncing
May 19, 2025

Build Offline-First Expo Apps: Caching, Storage & Syncing

Building mobile apps that work offline is no longer a luxury—it's a necessity. Users often encounter situations with poor or no internet connectivity. Whether they’re underground, traveling, or in remote areas, they expect your app to work regardless of network status. This blog explores how to build offline-ready Expo apps using powerful tools like AsyncStorage, NetInfo, and smart syncing strategies.


🚀 Why Offline Support Matters

Imagine using a to-do list or note-taking app only to lose access because of bad internet. Poor offline support leads to bad user experience and app uninstalls. Offline support ensures:

  • Seamless user experience
  • Higher retention and engagement
  • Functional core features even in airplane mode
  • Better perception of reliability and polish

Apps like WhatsApp, Google Docs, and Notion are great examples—they continue to work even without a connection and sync data when the internet is back.


🔧 Tools We’ll Use

To enable offline support, we’ll combine a few key tools:

  • AsyncStorage: For local data persistence
  • NetInfo: To detect connection status
  • State management (e.g. useState / Redux / Zustand)
  • Syncing logic: To store changes and push them when online

🛠️ Step-by-Step: Building a Basic Offline Note App

Let’s build a simple note-taking app that allows users to write notes offline, store them locally, and sync them when the app detects internet access.

Step 1: Create a New Expo Project

expo init offline-notes-app
cd offline-notes-app

Choose the blank template (JavaScript or TypeScript).


Step 2: Install Dependencies

npm install @react-native-async-storage/async-storage @react-native-community/netinfo

These libraries help us store and retrieve data locally, and detect network changes.


Step 3: Setup AsyncStorage

Create a utility file to manage local storage.

utils/storage.js

import AsyncStorage from "@react-native-async-storage/async-storage";

export const saveNotes = async (notes) => {
  try {
    const json = JSON.stringify(notes);
    await AsyncStorage.setItem("@notes", json);
  } catch (e) {
    console.error("Saving error:", e);
  }
};

export const loadNotes = async () => {
  try {
    const json = await AsyncStorage.getItem("@notes");
    return json != null ? JSON.parse(json) : [];
  } catch (e) {
    console.error("Loading error:", e);
    return [];
  }
};

Step 4: Check Connectivity with NetInfo

Detect network status and sync accordingly.

hooks/useNetwork.js

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

export const useNetworkStatus = () => {
  const [isConnected, setIsConnected] = useState(true);

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

  return isConnected;
};

Step 5: Implement Offline Notes in App.js

import React, { useEffect, useState } from "react";
import {
  View,
  TextInput,
  Button,
  FlatList,
  Text,
  StyleSheet,
} from "react-native";
import { loadNotes, saveNotes } from "./utils/storage";
import { useNetworkStatus } from "./hooks/useNetwork";

export default function App() {
  const [note, setNote] = useState("");
  const [notes, setNotes] = useState([]);
  const isConnected = useNetworkStatus();

  useEffect(() => {
    (async () => {
      const localNotes = await loadNotes();
      setNotes(localNotes);
    })();
  }, []);

  useEffect(() => {
    saveNotes(notes);
  }, [notes]);

  const addNote = () => {
    if (!note.trim()) return;
    const newNote = { id: Date.now().toString(), content: note };
    setNotes((prev) => [newNote, ...prev]);
    setNote("");
  };

  return (
    <View style={styles.container}>
      <TextInput
        style={styles.input}
        value={note}
        placeholder="Write a note..."
        onChangeText={setNote}
      />
      <Button title="Save Note" onPress={addNote} />

      {!isConnected && <Text style={styles.offline}>Offline Mode</Text>}

      <FlatList
        data={notes}
        keyExtractor={(item) => item.id}
        renderItem={({ item }) => (
          <Text style={styles.note}>{item.content}</Text>
        )}
      />
    </View>
  );
}

const styles = StyleSheet.create({
  container: { flex: 1, padding: 20, paddingTop: 60 },
  input: { borderWidth: 1, borderColor: "#ccc", padding: 10, marginBottom: 10 },
  note: { padding: 10, borderBottomWidth: 1, borderColor: "#eee" },
  offline: { color: "red", marginTop: 10 },
});

🔁 Smart Syncing Strategy

Once connectivity is restored, you can:

  • Push new or changed notes to your backend
  • Sync deletions or edits
  • Resolve conflicts (latest timestamp wins or manual conflict resolution)

You might track unsynced changes in a separate array and attempt syncing when isConnected is true.

Example Sync Logic (Pseudo-code)

useEffect(() => {
  if (isConnected && pendingChanges.length > 0) {
    syncToServer(pendingChanges);
    clearPendingChanges();
  }
}, [isConnected]);

💡 Tips for Better Offline UX

  • Show a clear offline indicator
  • Queue user actions and process them when back online
  • Cache images, API responses, and forms
  • Use local-first design: write locally, then sync

🔮 What’s Next?

Take your offline experience even further:

  • Database layer: Use Realm or SQLite for structured offline data
  • Background sync: With expo-task-manager or push notifications
  • Service workers (PWA) if targeting the web
  • Delta sync: Send only what’s changed, not the full payload

✅ Conclusion

Building offline-ready apps with Expo is achievable and incredibly rewarding. By using tools like AsyncStorage and NetInfo, you ensure your app is always usable and never leaves the user stranded. Combine good UX with solid sync strategies and you're on your way to creating a world-class mobile experience.

Offline is not an afterthought. Build it in from the beginning, and your users will thank you.