Skip to content

Hooks

useOfflineMutation

Queue-aware mutation hook with built-in state tracking.

tsx
const {
  mutateOffline,
  status,
  isIdle,
  isLoading,
  isSuccess,
  isError,
  isQueued,
  error,
  reset,
} = useOfflineMutation<PayloadType>(actionName, options?)

Parameters

ParameterTypeDescription
actionNamestringUnique identifier for this action (e.g. 'LIKE_POST')
options.handler(payload) => Promise<void>API call for this action. Registered automatically, used during sync.
options.onOptimisticSuccess(payload) => voidFires immediately — update local state here
options.onSuccess(payload) => voidFires only after a successful direct call (online)
options.onError(error, payload) => voidFires if the direct API call fails while online

Returns

PropertyTypeDescription
mutateOffline(payload: T) => Promise<void>Execute the mutation
statusMutationStatus'idle' | 'loading' | 'success' | 'error' | 'queued'
isIdlebooleantrue before any mutation
isLoadingbooleantrue while the handler is running (online only)
isSuccessbooleantrue after a successful direct call
isErrorbooleantrue if the direct call threw
isQueuedbooleantrue when action was added to the offline queue
errorError | nullThe error that occurred, if any
reset() => voidReset status back to 'idle'

State Flow

ScenarioStatus
Online + successidleloadingsuccess
Online + API failsidleloadingqueued
Offlineidlequeued

Example

tsx
function LikeButton({ postId }) {
  const { mutateOffline, isLoading, isQueued } = useOfflineMutation('LIKE_POST', {
    handler: async (payload) => {
      await fetch('/api/likes', { method: 'POST', body: JSON.stringify(payload) });
    },
    onOptimisticSuccess: () => setLiked(true),
  });

  return (
    <Button
      title={isLoading ? '⏳' : isQueued ? '📡 Queued' : '❤️ Like'}
      onPress={() => mutateOffline({ postId })}
      disabled={isLoading}
    />
  );
}

useOfflineQueue

Access the live queue state. Built on useSyncExternalStore — only re-renders when the queue actually changes.

tsx
const { queue, pendingCount, isSyncing, syncNow, clearQueue } = useOfflineQueue()

Returns

PropertyTypeDescription
queueOfflineAction[]Current queue contents
pendingCountnumberNumber of pending items
isSyncingbooleanWhether a sync is in progress
syncNow() => Promise<void>Trigger a manual sync
clearQueue() => Promise<void>Remove all queued items

useNetworkStatus

Reactive connectivity status. Built on useSyncExternalStore — no Context, no cascading re-renders.

tsx
const { isOnline } = useNetworkStatus()

Returns

PropertyTypeDescription
isOnlineboolean | nulltrue = online, false = offline, null = not yet detected

Re-render guarantee

If your component doesn't call useNetworkStatus(), it will never re-render due to connectivity changes. Only subscribers re-render.

Example

tsx
function ConnectionBanner() {
  const { isOnline } = useNetworkStatus();

  if (isOnline !== false) return null;

  return (
    <View style={{ backgroundColor: '#ff4444', padding: 8 }}>
      <Text style={{ color: 'white', textAlign: 'center' }}>
        📴 You are offline
      </Text>
    </View>
  );
}

useSyncProgress

Live progress tracking during a sync session.

tsx
const progress = useSyncProgress()

Returns

PropertyTypeDescription
isActivebooleanIs a sync session running?
totalCountnumberTotal items in this batch
completedCountnumberSuccessfully synced
failedCountnumberItems that failed
percentagenumber0–100
currentActionOfflineAction | nullThe action being synced right now
itemsSyncProgressItem[]Per-item status array

Example

tsx
function SyncProgressUI() {
  const { isActive, percentage, items } = useSyncProgress();

  if (!isActive) return null;

  return (
    <View>
      <Text>Syncing... {percentage}%</Text>
      {items.map((item) => (
        <Text key={item.action.id}>
          {item.status === 'success' ? '✅' : item.status === 'failed' ? '❌' : '⏳'}
          {' '}{item.action.actionName}
        </Text>
      ))}
    </View>
  );
}