@resilient

useBackgroundSync

Queue requests while offline and automatically sync them when connectivity is restored.

Live Demo

Network

Online

Queue

0 pending

Status

Idle

Synced

0

Message Queue

No messages queued yet

How to test

Open DevTools → Network tab → Select "Offline". Queue some messages, then go back online. Messages will automatically sync when the connection is restored.

Installation

npm install react-resilient-hooks

Usage

import { useBackgroundSync } from 'react-resilient-hooks';

function MyComponent() {
  const { status, enqueue, flush, abortFlush, getQueueSize } = useBackgroundSync({
    onSuccess: (req) => console.log('Synced:', req.id),
    onError: (req, error) => console.error('Failed:', req.id, error),
    onRetry: (req, attempt) => console.log(`Retry #${attempt}`, req.url),
    retry: {
      maxRetries: 5,
      retryDelay: (attempt) => Math.min(1000 * 2 ** attempt, 30000),
    },
    concurrency: 3,
    maxQueueSize: 100,
    onQueueFull: 'drop-oldest',
  });

  const handleSubmit = async (data) => {
    try {
      await fetch('/api/submit', { method: 'POST', body: JSON.stringify(data) });
    } catch {
      // Queue for later if network fails
      await enqueue('/api/submit', {
        method: 'POST',
        body: JSON.stringify(data),
      });
    }
  };

  const isSyncing = status.status === 'loading';

  return (
    <div>
      <p>Status: {status.status}</p>
      <button onClick={() => handleSubmit({ message: 'Hello' })}>Submit</button>
      <button onClick={flush} disabled={isSyncing}>Flush</button>
      {isSyncing && <button onClick={abortFlush}>Abort</button>}
    </div>
  );
}

API

Options

queueStore?QueueStore<QueuedReq>

Custom queue store implementation. Defaults to IndexedDBQueueStore for persistence.

onSuccess?(req: QueuedReq) => void

Callback fired when a request is successfully synced.

onError?(req: QueuedReq, error: Error) => void

Callback fired when a request fails after all retries.

onRetry?(req: QueuedReq, attempt: number, error: Error) => void

Callback fired when a request is being retried.

retry?RetryPolicy

Retry configuration: maxRetries (default: 3), retryDelay, shouldRetry.

concurrency?number

Number of concurrent requests during flush (default: 3).

maxQueueSize?number

Maximum number of items in queue (default: unlimited).

onQueueFull?'drop-oldest' | 'reject'

Behavior when queue is full (default: drop-oldest).

debug?boolean | ((msg: string, data?: unknown) => void)

Enable debug logging or provide custom logger.

Returns

statusResilientResult

Current sync status: idle, loading, success, or error.

enqueue(url: string, options?: RequestInit, meta?: Record<string, unknown>) => Promise<string>

Add a request to the queue. Returns the request ID.

flush() => Promise<FlushResult>

Manually trigger sync. Returns succeeded/failed/pending counts.

abortFlush() => void

Cancel the current flush operation.

getQueueSize() => Promise<number>

Get current number of requests in the queue.

clearQueue() => Promise<void>

Clear all queued requests.

Queue Stores

The hook supports different storage backends for the request queue:

  • IndexedDBQueueStore (default): Persists across page reloads and browser restarts
  • MemoryQueueStore: Fast but volatile - lost on page reload
import { MemoryQueueStore } from 'react-resilient-hooks';

const { enqueue } = useBackgroundSync({
  queueStore: new MemoryQueueStore(),
});

Flush Result

const result = await flush();
// {
//   succeeded: 5,   // Successfully synced
//   failed: 1,      // Failed after all retries
//   pending: 2,     // Still in queue (added during flush)
//   errors: [{ req, error, statusCode, attempts }]
// }

How It Works

  1. When you call enqueue(), the request is stored in IndexedDB
  2. When connectivity is restored, queued requests are automatically flushed
  3. Requests are processed in parallel (configurable concurrency)
  4. Failed requests are retried with exponential backoff
  5. After max retries, failed requests are reported in the flush result
  6. You can abort a flush in progress with abortFlush()

This ensures no data is lost even when the user goes offline unexpectedly.