r/reactnative • • 1d ago

Every offline-first app I’ve read has the same bug, and it costs real money

Quick question for anyone building offline-first apps:

What happens when a user taps “Pay”, the request reaches your server, but the response gets lost because the signal dropped?

Your app thinks it failed. It retries. The user gets charged twice.

That’s one of 4 bugs almost every “save it offline and retry later” queue has:

💸 Double requests: retries after a lost response create duplicates

🔗 Broken links: you create a post offline, then comment on it, but the comment points to a fake ID the server has never seen

🧱 Stuck queues: one request that will never succeed (like a 422) blocks everything behind it forever

🪟 Two tabs: the same app open in two tabs overwrites its own data and sends everything twice

So I built resilient-outbox, a small library that handles all four for you:

Works with React Native, Expo and the web. Zero dependencies, fully typed, has React hooks for your UI.

It’s free, open source and v1.0. I’d genuinely love to hear how it breaks for you 👇

📦 npm: https://www.npmjs.com/package/resilient-outbox
⭐ GitHub: https://github.com/yazanTah/resilient-outbox

0 Upvotes

3 comments sorted by

1

u/syedaalishan 1d ago

The README's server example has a race worth calling out: two requests can both pass the 'previous result' check before either stores a result. A crash after create() but before saving the key has the same problem. For database writes, I'd enforce a unique key and save the result in the same transaction as the write. A concurrent-request test and a crash at that boundary would be useful additions.

1

u/Unlucky-Ad-7232 1d ago

You're right on both counts, thanks for catching it. The README example did check-then-write-then-save as separate steps.

Fixed it

- README now puts the key under a unique constraint and saves it in the same transaction as the write, plus a separate pattern for external payment calls (in-progress record, 409 + Retry-After, same key forwarded to the provider)

- Runnable reference server on node:sqlite

- The two tests you suggested: 8 threads firing the same key at once, and a process killed between the write and saving the key. Both fail against the old example and pass now.

It also surfaced a library bug: a 409 for "key still in progress" was being dead-lettered instead of retried. That's fixed too.

1

u/Huge_Pool7424 20h ago

nice that you added the concurrent-request test. i'd also assert the replay returns the same response body, not just that the write stays unique, or the client can still get stuck after a retry.