Offline state corrupting data
What it looks like
// On reconnect, push whatever is cached.
for (const item of pendingEdits) {
await supabase.from("orders").upsert(item); // no version, no timestamp.
}
// Elsewhere: two screens each write the full record.
await db.runAsync("UPDATE orders SET status = ?, total = ?", status, total);What happens
Users lose edits they watched themselves save, records end up half-updated, and nobody can tell which copy of the data is the real one.
Where it shows up
Descriptive, not a verdict: builders and stacks whose defaults make this reachable, or that a public incident names. Each hub states the tool's own security features.
Check it yourself
Uses your builder's dashboard and your browser. No tools to install.
- On two phones (or a phone and the simulator) signed into the same account, put one in airplane mode and edit the same record on both.
- Change a second field on the online device, then reconnect the offline one and wait for sync.
- Open the record on both devices and in your database dashboard and compare every field.
- A bad result is a field silently reverting, the two devices showing different data, or a record that shows half of each edit.
What we check
We drive the app through scripted interruptions: killing it mid-save, dropping the connection during sync, and replaying the same queued edit twice. The report shows each resulting database state next to what the user saw on screen.
Fix direction
Give every record an updated-at or version field and make the server reject or merge stale writes. Wrap multi-step local writes in an exclusive transaction, and make queued edits idempotent (safe to replay) so a second delivery does no harm. Sync libraries such as WatermelonDB expose a conflict resolver and per-column change tracking; Expo SQLite provides exclusive transactions.
Mechanism documented in: Expo Docs: SQLite (transactions) (verified 2026-09-21); WatermelonDB Docs: Synchronization, frontend (verified 2026-09-21).
Related patterns
- Race conditions on counters and balancesData integrityHigh
Users can spend the same credits twice, book the last seat twice, or redeem a coupon more times than allowed, and the stored totals stop matching what was actually given out.
- No idempotency on payment handlersPaymentsHigh
A customer is charged twice after a network hiccup, or a replayed webhook tops up an account again and again for a single payment.
- Retries with no pause and no capReliabilityMedium
One outage turns into a flood of retries from every open browser tab, which keeps the service down longer, runs up usage bills and can submit the same order or payment twice.
- Deletes with no soft delete or audit trailData integrityHigh
One wrong click, a bug in a filter, or an abused account removes customer data for good, and nobody can say afterwards what was lost or who removed it.
Questions
why does my app lose changes after going offline
The usual cause is a sync routine that replays cached edits without a version or timestamp, so a device that reconnects later overwrites edits made in between. Add an updated-at field, have the server compare it before writing, and test with two devices editing the same record while one is in airplane mode.
how do i test offline sync in a react native app
Use two signed-in devices, edit the same record on both with one offline, reconnect and compare each field on both screens and in the database. Then kill the app during a save and reopen it. Any reverted field or half-written record is a finding to fix before launch.
what is a conflict resolver in watermelondb
WatermelonDB's synchronize function pulls all changes since the last sync and pushes local ones, and accepts a conflictResolver option that decides how a record is updated when it changed in both places. Without one, you rely on the default merge and should test that it matches what your users expect.
Back to the catalogue, or run the self-check for your stack.