People log meals and workouts in gyms, on hiking trails, in hospital waiting rooms — exactly the places where connectivity is worst. If your React health app assumes a live connection to save every entry, you've built something that fails its users at the moment they most need it to work.
A minimal offline-first pattern
You don't need a full sync engine to start. A local queue plus optimistic UI gets you most of the way there:
function useOfflineQueue(syncFn) {
const [queue, setQueue] = useState(() => {
const saved = localStorage.getItem("pending-sync");
return saved ? JSON.parse(saved) : [];
});
useEffect(() => {
localStorage.setItem("pending-sync", JSON.stringify(queue));
}, [queue]);
useEffect(() => {
if (!navigator.onLine || queue.length === 0) return;
const flush = async () => {
for (const item of queue) {
try {
await syncFn(item);
setQueue((prev) => prev.filter((q) => q.id !== item.id));
} catch {
break; // stop and retry later, preserve order
}
}
};
flush();
}, [queue, syncFn]);
const enqueue = (item) => setQueue((prev) => [...prev, { ...item, id: crypto.randomUUID() }]);
return { enqueue, pendingCount: queue.length };
}
The break on failure is deliberate — retrying out of order can corrupt a meal log's timeline. It's slower to recover from a bad connection, but it protects the thing users actually care about: an accurate history.
Where teams cut corners
The tempting shortcut is to show a spinner and block the UI until the sync succeeds. Don't. The entry should appear logged immediately, with a quiet, non-alarming indicator that it's pending sync. A user who just finished a workout doesn't want to babysit a network request.
The tradeoff
Building this properly costs you real engineering time — conflict resolution, queue persistence, retry backoff. For a side project or MVP, a simpler "save and hope" approach might be the right call. But if your app markets itself around reliability for people managing real health routines, offline handling isn't a nice-to-have feature. It's the actual product.
Top comments (0)