The Problem We're Solving
Let's be honest - mobile apps need to work offline. House hunters are going to be in basements, on subways, or in areas with terrible WiFi. They'll want to add house listings or take notes during open home viewings, and they shouldn't have to wait for a network connection.
So how do we build an app that works seamlessly offline, but also syncs everything back to the server when connectivity returns? That's what we're diving into in my new app "MyNextHome".
The Big Picture
Our house hunting app has three main pieces:
- HousesContext - The brain that manages all the houses and handles syncing
- Add/Edit Form - Where users create new listings
- House Details Screen - Where users take notes on each house
Everything is stored locally first (using AsyncStorage), then synced to the server when possible. Simple, right?
The Core Ideas
1. Save Locally, Always
No matter what, when a user saves something, it goes straight to local storage. No waiting, no "please check your connection" errors. The app just works.
2. Server is the Boss (Mostly)
The server has the definitive data - titles, locations, prices, all that good stuff. When we sync, we use server data as the base and merge in any local edits (like notes). This keeps everything consistent across devices.
3. Timestamps Tell the Story
Each house has two timestamps:
-
updatedAt- When you last changed it locally -
syncedAt- When it was last successfully sent to the server
If updatedAt > syncedAt (or syncedAt is missing), that house needs to be synced. It's like a to-do list, but for data.
4. Auto-Sync When Network Returns
The app watches for network changes. Go from offline to online? Boom - automatic sync of everything that needs it.
Data Flow Diagrams
First Load: Online
┌─────────────┐
│ App Start │
└──────┬──────┘
│
▼
┌─────────────────────┐
│ Load from AsyncStorage│ ← Instant UI (local data)
│ (if exists) │
└──────┬──────────────┘
│
▼
┌─────────────────────┐
│ Fetch from Server │ ← Background fetch
│ GET /api/open-homes │
└──────┬──────────────┘
│
▼
┌─────────────────────┐
│ Merge Data │
│ - Server = base │
│ - Local notes kept │
└──────┬──────────────┘
│
▼
┌─────────────────────┐
│ Save to AsyncStorage│
│ Update UI │
└─────────────────────┘
What happens: You get instant UI from local data, then fresh server data merges in the background. Your notes are preserved.
First Load: Offline
┌─────────────┐
│ App Start │
└──────┬──────┘
│
▼
┌─────────────────────┐
│ Load from AsyncStorage│ ← All you get
│ (if exists) │
└──────┬──────────────┘
│
▼
┌─────────────────────┐
│ Try Fetch from Server│
│ ❌ Network Error │
└──────┬──────────────┘
│
▼
┌─────────────────────┐
│ Show Local Data │ ← Still works!
│ (maybe show error) │
└─────────────────────┘
What happens: You only see local data, but the app still works. When you come back online, it'll fetch fresh data.
Updating Notes: Online
┌─────────────────┐
│ User edits notes│
│ Taps "Save" │
└────────┬────────┘
│
▼
┌─────────────────────┐
│ updateHouse() │
│ - Update local state│
│ - Set updatedAt │
│ - Save to AsyncStorage│
└────────┬────────────┘
│
▼
┌─────────────────────┐
│ Check Network │
│ ✅ Online │
└────────┬────────────┘
│
▼
┌─────────────────────┐
│ syncToServer() │
│ - Find dirty houses │
│ - POST to server │
│ - Update syncedAt │
└────────┬────────────┘
│
▼
┌─────────────────────┐
│ Navigate back │
│ ✅ Notes saved & synced│
└─────────────────────┘
What happens: Notes save locally immediately, then sync to server right away. Smooth and fast.
Updating Notes: Offline
┌─────────────────┐
│ User edits notes│
│ Taps "Save" │
└────────┬────────┘
│
▼
┌─────────────────────┐
│ updateHouse() │
│ - Update local state│
│ - Set updatedAt │
│ - Save to AsyncStorage│
└────────┬────────────┘
│
▼
┌─────────────────────┐
│ Check Network │
│ ❌ Offline │
└────────┬────────────┘
│
▼
┌─────────────────────┐
│ Navigate back │
│ ✅ Notes saved locally│
└────────┬────────────┘
│
▼
...
│
▼
┌─────────────────────┐
│ Network comes back │
│ NetInfo detects │
└────────┬────────────┘
│
▼
┌─────────────────────┐
│ syncToServer() │
│ - updatedAt > syncedAt│
│ - POST to server │
│ - Update syncedAt │
└─────────────────────┘
What happens: Notes save locally, no sync attempt. Later, when network returns, automatic sync kicks in and pushes your notes to the server.
Server Updates (New Houses)
┌─────────────────────┐
│ Server has new house│
│ (added by admin) │
└────────┬────────────┘
│
▼
┌─────────────────────┐
│ App fetches on load │
│ or background refresh│
└────────┬────────────┘
│
▼
┌─────────────────────┐
│ Transform API data │
│ to House format │
└────────┬────────────┘
│
▼
┌─────────────────────┐
│ Merge with local │
│ - Server = base │
│ - Keep local notes │
└────────┬────────────┘
│
▼
┌─────────────────────┐
│ Save to AsyncStorage│
│ Update UI │
└─────────────────────┘
What happens: New houses from the server appear in your app. If you had local notes for that house ID, they're preserved. Server data is the source of truth for everything else.
Adding New House: Offline → Online
┌─────────────────┐
│ User adds house │
│ (offline) │
└────────┬────────┘
│
▼
┌─────────────────────┐
│ addHouse() │
│ - Create with ID │
│ - Set updatedAt │
│ - No syncedAt │
│ - Save locally │
└────────┬────────────┘
│
▼
┌─────────────────────┐
│ House appears in UI │
│ ✅ Works offline! │
└────────┬────────────┘
│
... (user goes online)
│
▼
┌─────────────────────┐
│ NetInfo detects │
│ offline → online │
└────────┬────────────┘
│
▼
┌─────────────────────┐
│ syncToServer() │
│ - syncedAt missing │
│ - POST to server │
│ - Set syncedAt │
└─────────────────────┘
What happens: You add a house offline, it appears immediately. When you come back online, it automatically syncs to the server.
The Magic: Timestamp Comparison
The sync logic is pretty simple:
// Does this house need syncing?
const needsSync = !house.syncedAt ||
new Date(house.updatedAt) > new Date(house.syncedAt);
If syncedAt is missing, it's never been synced. If updatedAt is newer than syncedAt, you've made local changes since the last sync. Either way, it needs to go to the server.
Why This Works
- Reliability - Your data is never lost. Even if sync fails, it's safe in local storage.
- Speed - No waiting for network. Everything feels instant.
- User Experience - The app just works, online or offline.
- Consistency - Server is the source of truth, so everyone sees the same base data.
- Simplicity - Timestamp comparison is easy to understand and debug.
Wrapping Up
This offline-first approach means your users can use the app anywhere - subway, basement viewing, terrible WiFi, you name it. Data saves locally first, syncs when it can, and the server keeps everything consistent.
The key is: local storage is your friend, timestamps are your guide, and the server is your source of truth.
Built with @react-native-async-storage/async-storage for local persistence and @react-native-community/netinfo for network detection.
Top comments (5)
This is super impressive — the way you’ve designed the offline-first flow is clean, practical, and really user-friendly.
Thanks! I’ve been trying to share mobile patterns in a clean, practical way, especially the kinds of things I wish I had when working on real-world apps. I’m glad the offline-first flow came across clearly. Appreciate you taking the time to comment!
I will keep my fingers crossed for you!
Congratulations on your work!
I wish you luck!
If this helped you, I’m writing more practical React Native troubleshooting guides. Follow me here or ask questions — happy to help others avoid the pain points I experienced.