Offline-first mobile apps with React Native
What a field app has to keep on the device, and how it catches up when the signal returns.

The phone is the source of truth
An offline-first app is not an online app with a spinner and a retry button. The record the user just created has to exist on the device before any request leaves, and the screen has to be willing to show that local record as the current truth. If the interface only works once the server has nodded, it will fail in the exact places it was built for.
We see this on field tools: inspections, deliveries, clinic notes, anything done in a corridor or a car park. The network is a sync channel. It is not the database the screen is reading.
If the screen only works behind a spinner, it is not offline-first. Save locally first. Sync when you can.
What stays on the device
Not everything belongs in the local store. We keep the records the user creates or must read to finish today’s job, and we keep enough reference data to make those screens honest. A full replica of the back office, refreshed every minute, will fill the phone and still be stale in the one field that mattered.
- Drafts and completed records the user just wrote.
- The reference rows those screens need, with a clear age.
- Photos and files queued beside the record, not in a separate “uploads” mystery.
- Nothing the user is not allowed to see once the device is unlocked.
The sync queue
Every local write becomes an item on a queue: create, update, or delete, with a client id the server can recognise when the same item arrives twice. The queue drains when the radio is back. The screen never waits for the drain to show the user their own work.
queue.ts
await db.transaction(async (tx) => {
await tx.save(record);
await tx.enqueue({
op: "upsert",
clientId: record.id,
payload: record,
});
});Order matters inside one record and should not matter across unrelated ones. A photo that belongs to a note waits for the note’s id, or travels with a client id the server will reconcile. “Upload later” as a loose promise, with no queue and no identity, is how field data goes missing.
Conflicts, written down
Two people will edit the same job. We decide the rule before the first release: last write wins, a field-level merge, or a stop that asks the user. The wrong answer is to discover the rule in production, after a driver’s note has been overwritten by an office edit they never saw.
“Offline is a product decision. The queue, the local record, and the conflict rule are the feature.”M Jhanzaib, CEO
Get the next build note
in your inbox.
One practical email a month. No fluff, unsubscribe anytime.


