@mustafaaksoy41/react-native-offline-queue 0.1.3 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -307,39 +307,34 @@ Each `handler` is self-contained: when the user goes offline, actions are queued
307
307
 
308
308
  ### Using with React Query (TanStack Query)
309
309
 
310
- If you already use React Query, this package works alongside it. The key: **share the same API function** for both React Query mutations and the offline queue handler.
310
+ Use your **existing `useMutation` hook** the handler calls `mutateAsync`. No duplicate fetch, no extra API layer. Your mutation does everything (POST, cache invalidation, etc.).
311
311
 
312
- **Pattern 1 Shared API layer (recommended)**
313
-
314
- Define your API calls in one place. Use them in the offline queue `handler` and optionally in React Query's `useMutation`:
312
+ **Pattern — Handler uses `mutateAsync` from `useMutation`**
315
313
 
316
314
  ```tsx
317
- // api/posts.ts
318
- export async function createPost(payload: { title: string; body: string }) {
319
- const res = await fetch('https://api.myapp.com/posts', {
320
- method: 'POST',
321
- headers: { 'Content-Type': 'application/json' },
322
- body: JSON.stringify(payload),
323
- });
324
- if (!res.ok) throw new Error(await res.text());
325
- return res.json();
326
- }
327
-
328
- // CreatePostForm.tsx
329
- import { useQueryClient } from '@tanstack/react-query';
315
+ import { useMutation, useQueryClient } from '@tanstack/react-query';
330
316
  import { useOfflineMutation } from '@mustafaaksoy41/react-native-offline-queue';
331
- import { createPost } from './api/posts';
332
317
 
333
318
  function CreatePostForm() {
334
319
  const queryClient = useQueryClient();
335
320
 
321
+ // Your existing React Query mutation — mutationFn, onSuccess, retry, etc.
322
+ const { mutateAsync } = useMutation({
323
+ mutationFn: (payload: { title: string; body: string }) =>
324
+ fetch('https://api.myapp.com/posts', {
325
+ method: 'POST',
326
+ headers: { 'Content-Type': 'application/json' },
327
+ body: JSON.stringify(payload),
328
+ }).then((r) => r.json()),
329
+ onSuccess: () => queryClient.invalidateQueries({ queryKey: ['posts'] }),
330
+ });
331
+
332
+ // Handler = your mutation. Online: runs immediately. Offline: queued, runs when back online.
336
333
  const { mutateOffline } = useOfflineMutation('CREATE_POST', {
337
334
  handler: async (payload) => {
338
- await createPost(payload);
339
- queryClient.invalidateQueries({ queryKey: ['posts'] });
335
+ await mutateAsync(payload);
340
336
  },
341
337
  onOptimisticSuccess: (payload) => {
342
- // Add to local list immediately (optimistic update)
343
338
  queryClient.setQueryData(['posts'], (old: any) =>
344
339
  old ? [...old, { ...payload, id: 'temp', pending: true }] : [payload]
345
340
  );
@@ -355,62 +350,40 @@ function CreatePostForm() {
355
350
  }
356
351
  ```
357
352
 
358
- **Pattern 2 Centralized `onSyncAction` with React Query**
353
+ **With custom hooks / API layer**
359
354
 
360
- Handle all actions in one place and use `queryClient` for cache updates:
355
+ If your mutations live in custom hooks:
361
356
 
362
357
  ```tsx
363
- // App.tsx
364
- import { useQueryClient } from '@tanstack/react-query';
365
- import { OfflineProvider } from '@mustafaaksoy41/react-native-offline-queue';
366
- import { createPost, likePost } from './api';
367
-
368
- function AppWithProviders() {
358
+ // hooks/useCreatePost.ts
359
+ export function useCreatePost() {
369
360
  const queryClient = useQueryClient();
370
-
371
- return (
372
- <OfflineProvider
373
- config={{
374
- storageType: 'mmkv',
375
- syncMode: 'auto',
376
- onSyncAction: async (action) => {
377
- switch (action.actionName) {
378
- case 'CREATE_POST':
379
- await createPost(action.payload);
380
- queryClient.invalidateQueries({ queryKey: ['posts'] });
381
- break;
382
- case 'LIKE_POST':
383
- await likePost(action.payload);
384
- queryClient.invalidateQueries({ queryKey: ['posts'] });
385
- break;
386
- }
387
- },
388
- }}
389
- >
390
- <YourApp />
391
- </OfflineProvider>
392
- );
361
+ return useMutation({
362
+ mutationFn: api.createPost, // your axios/fetch wrapper
363
+ onSuccess: () => queryClient.invalidateQueries({ queryKey: ['posts'] }),
364
+ });
393
365
  }
394
366
 
395
- // Wrap with QueryClientProvider
396
- export default function App() {
397
- return (
398
- <QueryClientProvider client={queryClient}>
399
- <AppWithProviders />
400
- </QueryClientProvider>
401
- );
367
+ // CreatePostForm.tsx
368
+ function CreatePostForm() {
369
+ const { mutateAsync } = useCreatePost();
370
+
371
+ const { mutateOffline } = useOfflineMutation('CREATE_POST', {
372
+ handler: async (payload) => await mutateAsync(payload),
373
+ onOptimisticSuccess: (payload) => { /* ... */ },
374
+ });
375
+
376
+ return <Button onPress={() => mutateOffline({ title, body })} />;
402
377
  }
403
378
  ```
404
379
 
405
380
  **Summary**
406
381
 
407
- | What | Use |
408
- |------|-----|
409
- | API call (fetch/axios) | Same function in both `mutationFn` and `handler` |
410
- | Cache invalidation | `queryClient.invalidateQueries()` inside `handler` or `onSyncAction` |
411
- | Optimistic updates | `onOptimisticSuccess` + `queryClient.setQueryData()` |
382
+ | Handler does | Your mutation does |
383
+ |--------------|--------------------|
384
+ | Calls `mutateAsync(payload)` | POST/GET, retry, cache invalidation, error handling |
412
385
 
413
- React Query handles **queries** (reading data). This package handles **mutations when offline** (queue + sync). They work together.
386
+ No duplicate logic. Same mutation for online and offline sync.
414
387
 
415
388
  ## Configuration
416
389
 
@@ -502,31 +475,94 @@ Omit `onOnlineRestore` entirely. Nothing happens — you handle sync manually th
502
475
 
503
476
  ### `useOfflineMutation(actionName, options?)`
504
477
 
505
- Queue-aware mutation hook. Calls the handler directly when online, queues when offline.
478
+ Queue-aware mutation hook with built-in state tracking. Calls the handler directly when online, queues when offline.
506
479
 
507
- ```tsx
508
- const { mutateOffline } = useOfflineMutation('CREATE_POST', {
509
- handler: async (payload) => {
510
- // Your API call — runs directly when online, or during sync when offline
511
- await api.createPost(payload);
512
- },
513
- onOptimisticSuccess: (payload) => {
514
- // Runs immediately — update your local state here
515
- },
516
- onError: (error, payload) => {
517
- // Runs if the direct API call fails while online
518
- },
519
- });
480
+ **Returns:**
520
481
 
521
- mutateOffline({ title: 'Hello', body: 'World' });
482
+ ```tsx
483
+ const {
484
+ mutateOffline, // (payload) => Promise<void>
485
+ status, // 'idle' | 'loading' | 'success' | 'error' | 'queued'
486
+ isIdle, // true before any mutation
487
+ isLoading, // true while the handler is running (online only)
488
+ isSuccess, // true after a successful direct call
489
+ isError, // true if the direct call threw (action still queued as fallback)
490
+ isQueued, // true when the action was added to the offline queue
491
+ error, // Error | null
492
+ reset, // () => void — reset status back to idle
493
+ } = useOfflineMutation('ACTION_NAME', options);
522
494
  ```
523
495
 
524
496
  | Option | Type | Description |
525
497
  |--------|------|-------------|
526
- | `handler` | `(payload) => Promise<void>` | API call for this specific action. Registered automatically. |
527
- | `onOptimisticSuccess` | `(payload) => void` | Fires immediately for instant UI updates |
498
+ | `handler` | `(payload) => Promise<void>` | API call for this action. Registered automatically, used during sync. |
499
+ | `onOptimisticSuccess` | `(payload) => void` | Fires immediately update your local state here |
500
+ | `onSuccess` | `(payload) => void` | Fires only after a successful direct call (online) |
528
501
  | `onError` | `(error, payload) => void` | Fires if the direct call fails while online |
529
502
 
503
+ **With `fetch`:**
504
+
505
+ ```tsx
506
+ function LikeButton({ postId }) {
507
+ const { mutateOffline, isLoading, isQueued } = useOfflineMutation('LIKE_POST', {
508
+ handler: async (payload) => {
509
+ await fetch('/api/likes', {
510
+ method: 'POST',
511
+ body: JSON.stringify(payload),
512
+ });
513
+ },
514
+ onOptimisticSuccess: () => setLiked(true),
515
+ });
516
+
517
+ return (
518
+ <Button
519
+ title={isLoading ? '⏳' : isQueued ? '📡 Queued' : '❤️ Like'}
520
+ onPress={() => mutateOffline({ postId })}
521
+ disabled={isLoading}
522
+ />
523
+ );
524
+ }
525
+ ```
526
+
527
+ **With React Query:**
528
+
529
+ ```tsx
530
+ import { useMutation, useQueryClient } from '@tanstack/react-query';
531
+
532
+ function LikeButton({ postId }) {
533
+ const queryClient = useQueryClient();
534
+
535
+ const { mutateAsync } = useMutation({
536
+ mutationFn: (payload) => api.likePost(payload),
537
+ onSuccess: () => queryClient.invalidateQueries({ queryKey: ['posts'] }),
538
+ });
539
+
540
+ const { mutateOffline, isQueued } = useOfflineMutation('LIKE_POST', {
541
+ handler: async (payload) => await mutateAsync(payload),
542
+ onOptimisticSuccess: () => {
543
+ queryClient.setQueryData(['posts', postId], (old) => ({
544
+ ...old, liked: true,
545
+ }));
546
+ },
547
+ });
548
+
549
+ return (
550
+ <Button
551
+ title={isQueued ? '📡 Queued' : '❤️ Like'}
552
+ onPress={() => mutateOffline({ postId })}
553
+ />
554
+ );
555
+ }
556
+ ```
557
+
558
+ **State flow:**
559
+
560
+ | Scenario | `status` flow |
561
+ |----------|---------------|
562
+ | Online + success | `idle` → `loading` → `success` |
563
+ | Online + API fails | `idle` → `loading` → `queued` (fallback) |
564
+ | Offline | `idle` → `queued` |
565
+
530
566
  ### `useOfflineQueue()`
531
567
 
532
568
  Access the live queue state. Uses `useSyncExternalStore` under the hood — only re-renders when the queue actually changes.
@@ -13,6 +13,8 @@ function useOfflineMutation(actionName, options) {
13
13
  } = (0, _OfflineProvider.useNetworkStatus)();
14
14
  const handlerRef = (0, _react.useRef)(options?.handler);
15
15
  handlerRef.current = options?.handler;
16
+ const [status, setStatus] = (0, _react.useState)('idle');
17
+ const [error, setError] = (0, _react.useState)(null);
16
18
 
17
19
  // Register per-action handler (persists even after unmount)
18
20
  (0, _react.useEffect)(() => {
@@ -20,7 +22,11 @@ function useOfflineMutation(actionName, options) {
20
22
  _OfflineManager.OfflineManager.registerHandler(actionName, payload => handlerRef.current(payload));
21
23
  }
22
24
  }, [actionName]);
23
- const mutateOffline = async payload => {
25
+ const reset = (0, _react.useCallback)(() => {
26
+ setStatus('idle');
27
+ setError(null);
28
+ }, []);
29
+ const mutateOffline = (0, _react.useCallback)(async payload => {
24
30
  // Resolve which handler to use: per-action handler > global onSyncAction
25
31
  const handler = handlerRef.current || _OfflineManager.OfflineManager.getHandler(actionName);
26
32
  const globalHandler = _OfflineManager.OfflineManager.onSyncAction;
@@ -28,6 +34,8 @@ function useOfflineMutation(actionName, options) {
28
34
  if (isOnline && hasHandler) {
29
35
  // ── ONLINE: Execute directly, skip the queue ──
30
36
  if (__DEV__) console.log(`[OfflineQueue] mutate: ${actionName} (direct)`);
37
+ setStatus('loading');
38
+ setError(null);
31
39
  try {
32
40
  if (handler) {
33
41
  await handler(payload);
@@ -40,22 +48,37 @@ function useOfflineMutation(actionName, options) {
40
48
  retryCount: 0
41
49
  });
42
50
  }
51
+ setStatus('success');
43
52
  options?.onOptimisticSuccess?.(payload);
44
- } catch (error) {
45
- console.warn(`[OfflineQueue] mutate: ${actionName} failed, falling back to queue`, error);
53
+ options?.onSuccess?.(payload);
54
+ } catch (err) {
55
+ console.warn(`[OfflineQueue] mutate: ${actionName} failed, falling back to queue`, err);
56
+ // API failed even though online → fallback to queue
46
57
  await _OfflineManager.OfflineManager.push(actionName, payload);
58
+ setStatus('queued');
59
+ setError(err);
47
60
  options?.onOptimisticSuccess?.(payload);
48
- options?.onError?.(error, payload);
61
+ options?.onError?.(err, payload);
49
62
  }
50
63
  } else {
51
64
  // ── OFFLINE: Add to queue + optimistic update ──
52
65
  if (__DEV__) console.log(`[OfflineQueue] mutate: ${actionName} (queued)`);
53
66
  await _OfflineManager.OfflineManager.push(actionName, payload);
67
+ setStatus('queued');
68
+ setError(null);
54
69
  options?.onOptimisticSuccess?.(payload);
55
70
  }
56
- };
71
+ }, [actionName, isOnline, options]);
57
72
  return {
58
- mutateOffline
73
+ mutateOffline,
74
+ status,
75
+ isIdle: status === 'idle',
76
+ isLoading: status === 'loading',
77
+ isSuccess: status === 'success',
78
+ isError: status === 'error',
79
+ isQueued: status === 'queued',
80
+ error,
81
+ reset
59
82
  };
60
83
  }
61
84
  //# sourceMappingURL=useOfflineMutation.js.map
@@ -1 +1 @@
1
- {"version":3,"names":["_react","require","_OfflineManager","_OfflineProvider","useOfflineMutation","actionName","options","isOnline","useNetworkStatus","handlerRef","useRef","handler","current","useEffect","OfflineManager","registerHandler","payload","mutateOffline","getHandler","globalHandler","onSyncAction","hasHandler","__DEV__","console","log","id","createdAt","Date","now","retryCount","onOptimisticSuccess","error","warn","push","onError"],"sourceRoot":"../../../src","sources":["hooks/useOfflineMutation.ts"],"mappings":";;;;;;AAAA,IAAAA,MAAA,GAAAC,OAAA;AACA,IAAAC,eAAA,GAAAD,OAAA;AACA,IAAAE,gBAAA,GAAAF,OAAA;AAEO,SAASG,kBAAkBA,CAChCC,UAAkB,EAClBC,OAIC,EACD;EACA,MAAM;IAAEC;EAAS,CAAC,GAAG,IAAAC,iCAAgB,EAAC,CAAC;EACvC,MAAMC,UAAU,GAAG,IAAAC,aAAM,EAACJ,OAAO,EAAEK,OAAO,CAAC;EAC3CF,UAAU,CAACG,OAAO,GAAGN,OAAO,EAAEK,OAAO;;EAErC;EACA,IAAAE,gBAAS,EAAC,MAAM;IACd,IAAIJ,UAAU,CAACG,OAAO,EAAE;MACtBE,8BAAc,CAACC,eAAe,CAACV,UAAU,EAAGW,OAAY,IACtDP,UAAU,CAACG,OAAO,CAAEI,OAAO,CAC7B,CAAC;IACH;EACF,CAAC,EAAE,CAACX,UAAU,CAAC,CAAC;EAEhB,MAAMY,aAAa,GAAG,MAAOD,OAAiB,IAAK;IACjD;IACA,MAAML,OAAO,GAAGF,UAAU,CAACG,OAAO,IAAIE,8BAAc,CAACI,UAAU,CAACb,UAAU,CAAC;IAC3E,MAAMc,aAAa,GAAGL,8BAAc,CAACM,YAAY;IACjD,MAAMC,UAAU,GAAGV,OAAO,IAAIQ,aAAa;IAE3C,IAAIZ,QAAQ,IAAIc,UAAU,EAAE;MAC1B;MACA,IAAIC,OAAO,EAAEC,OAAO,CAACC,GAAG,CAAC,0BAA0BnB,UAAU,WAAW,CAAC;MACzE,IAAI;QACF,IAAIM,OAAO,EAAE;UACX,MAAMA,OAAO,CAACK,OAAO,CAAC;QACxB,CAAC,MAAM,IAAIG,aAAa,EAAE;UACxB,MAAMA,aAAa,CAAC;YAClBM,EAAE,EAAE,EAAE;YACNpB,UAAU;YACVW,OAAO;YACPU,SAAS,EAAEC,IAAI,CAACC,GAAG,CAAC,CAAC;YACrBC,UAAU,EAAE;UACd,CAAC,CAAC;QACJ;QACAvB,OAAO,EAAEwB,mBAAmB,GAAGd,OAAO,CAAC;MACzC,CAAC,CAAC,OAAOe,KAAU,EAAE;QACnBR,OAAO,CAACS,IAAI,CAAC,0BAA0B3B,UAAU,gCAAgC,EAAE0B,KAAK,CAAC;QACzF,MAAMjB,8BAAc,CAACmB,IAAI,CAAC5B,UAAU,EAAEW,OAAO,CAAC;QAC9CV,OAAO,EAAEwB,mBAAmB,GAAGd,OAAO,CAAC;QACvCV,OAAO,EAAE4B,OAAO,GAAGH,KAAK,EAAEf,OAAO,CAAC;MACpC;IACF,CAAC,MAAM;MACL;MACA,IAAIM,OAAO,EAAEC,OAAO,CAACC,GAAG,CAAC,0BAA0BnB,UAAU,WAAW,CAAC;MACzE,MAAMS,8BAAc,CAACmB,IAAI,CAAC5B,UAAU,EAAEW,OAAO,CAAC;MAC9CV,OAAO,EAAEwB,mBAAmB,GAAGd,OAAO,CAAC;IACzC;EACF,CAAC;EAED,OAAO;IAAEC;EAAc,CAAC;AAC1B","ignoreList":[]}
1
+ {"version":3,"names":["_react","require","_OfflineManager","_OfflineProvider","useOfflineMutation","actionName","options","isOnline","useNetworkStatus","handlerRef","useRef","handler","current","status","setStatus","useState","error","setError","useEffect","OfflineManager","registerHandler","payload","reset","useCallback","mutateOffline","getHandler","globalHandler","onSyncAction","hasHandler","__DEV__","console","log","id","createdAt","Date","now","retryCount","onOptimisticSuccess","onSuccess","err","warn","push","onError","isIdle","isLoading","isSuccess","isError","isQueued"],"sourceRoot":"../../../src","sources":["hooks/useOfflineMutation.ts"],"mappings":"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","ignoreList":[]}
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
 
3
- import { useEffect, useRef } from 'react';
3
+ import { useEffect, useRef, useState, useCallback } from 'react';
4
4
  import { OfflineManager } from '../core/OfflineManager';
5
5
  import { useNetworkStatus } from '../components/OfflineProvider';
6
6
  export function useOfflineMutation(actionName, options) {
@@ -9,6 +9,8 @@ export function useOfflineMutation(actionName, options) {
9
9
  } = useNetworkStatus();
10
10
  const handlerRef = useRef(options?.handler);
11
11
  handlerRef.current = options?.handler;
12
+ const [status, setStatus] = useState('idle');
13
+ const [error, setError] = useState(null);
12
14
 
13
15
  // Register per-action handler (persists even after unmount)
14
16
  useEffect(() => {
@@ -16,7 +18,11 @@ export function useOfflineMutation(actionName, options) {
16
18
  OfflineManager.registerHandler(actionName, payload => handlerRef.current(payload));
17
19
  }
18
20
  }, [actionName]);
19
- const mutateOffline = async payload => {
21
+ const reset = useCallback(() => {
22
+ setStatus('idle');
23
+ setError(null);
24
+ }, []);
25
+ const mutateOffline = useCallback(async payload => {
20
26
  // Resolve which handler to use: per-action handler > global onSyncAction
21
27
  const handler = handlerRef.current || OfflineManager.getHandler(actionName);
22
28
  const globalHandler = OfflineManager.onSyncAction;
@@ -24,6 +30,8 @@ export function useOfflineMutation(actionName, options) {
24
30
  if (isOnline && hasHandler) {
25
31
  // ── ONLINE: Execute directly, skip the queue ──
26
32
  if (__DEV__) console.log(`[OfflineQueue] mutate: ${actionName} (direct)`);
33
+ setStatus('loading');
34
+ setError(null);
27
35
  try {
28
36
  if (handler) {
29
37
  await handler(payload);
@@ -36,22 +44,37 @@ export function useOfflineMutation(actionName, options) {
36
44
  retryCount: 0
37
45
  });
38
46
  }
47
+ setStatus('success');
39
48
  options?.onOptimisticSuccess?.(payload);
40
- } catch (error) {
41
- console.warn(`[OfflineQueue] mutate: ${actionName} failed, falling back to queue`, error);
49
+ options?.onSuccess?.(payload);
50
+ } catch (err) {
51
+ console.warn(`[OfflineQueue] mutate: ${actionName} failed, falling back to queue`, err);
52
+ // API failed even though online → fallback to queue
42
53
  await OfflineManager.push(actionName, payload);
54
+ setStatus('queued');
55
+ setError(err);
43
56
  options?.onOptimisticSuccess?.(payload);
44
- options?.onError?.(error, payload);
57
+ options?.onError?.(err, payload);
45
58
  }
46
59
  } else {
47
60
  // ── OFFLINE: Add to queue + optimistic update ──
48
61
  if (__DEV__) console.log(`[OfflineQueue] mutate: ${actionName} (queued)`);
49
62
  await OfflineManager.push(actionName, payload);
63
+ setStatus('queued');
64
+ setError(null);
50
65
  options?.onOptimisticSuccess?.(payload);
51
66
  }
52
- };
67
+ }, [actionName, isOnline, options]);
53
68
  return {
54
- mutateOffline
69
+ mutateOffline,
70
+ status,
71
+ isIdle: status === 'idle',
72
+ isLoading: status === 'loading',
73
+ isSuccess: status === 'success',
74
+ isError: status === 'error',
75
+ isQueued: status === 'queued',
76
+ error,
77
+ reset
55
78
  };
56
79
  }
57
80
  //# sourceMappingURL=useOfflineMutation.js.map
@@ -1 +1 @@
1
- {"version":3,"names":["useEffect","useRef","OfflineManager","useNetworkStatus","useOfflineMutation","actionName","options","isOnline","handlerRef","handler","current","registerHandler","payload","mutateOffline","getHandler","globalHandler","onSyncAction","hasHandler","__DEV__","console","log","id","createdAt","Date","now","retryCount","onOptimisticSuccess","error","warn","push","onError"],"sourceRoot":"../../../src","sources":["hooks/useOfflineMutation.ts"],"mappings":";;AAAA,SAASA,SAAS,EAAEC,MAAM,QAAQ,OAAO;AACzC,SAASC,cAAc,QAAQ,wBAAwB;AACvD,SAASC,gBAAgB,QAAQ,+BAA+B;AAEhE,OAAO,SAASC,kBAAkBA,CAChCC,UAAkB,EAClBC,OAIC,EACD;EACA,MAAM;IAAEC;EAAS,CAAC,GAAGJ,gBAAgB,CAAC,CAAC;EACvC,MAAMK,UAAU,GAAGP,MAAM,CAACK,OAAO,EAAEG,OAAO,CAAC;EAC3CD,UAAU,CAACE,OAAO,GAAGJ,OAAO,EAAEG,OAAO;;EAErC;EACAT,SAAS,CAAC,MAAM;IACd,IAAIQ,UAAU,CAACE,OAAO,EAAE;MACtBR,cAAc,CAACS,eAAe,CAACN,UAAU,EAAGO,OAAY,IACtDJ,UAAU,CAACE,OAAO,CAAEE,OAAO,CAC7B,CAAC;IACH;EACF,CAAC,EAAE,CAACP,UAAU,CAAC,CAAC;EAEhB,MAAMQ,aAAa,GAAG,MAAOD,OAAiB,IAAK;IACjD;IACA,MAAMH,OAAO,GAAGD,UAAU,CAACE,OAAO,IAAIR,cAAc,CAACY,UAAU,CAACT,UAAU,CAAC;IAC3E,MAAMU,aAAa,GAAGb,cAAc,CAACc,YAAY;IACjD,MAAMC,UAAU,GAAGR,OAAO,IAAIM,aAAa;IAE3C,IAAIR,QAAQ,IAAIU,UAAU,EAAE;MAC1B;MACA,IAAIC,OAAO,EAAEC,OAAO,CAACC,GAAG,CAAC,0BAA0Bf,UAAU,WAAW,CAAC;MACzE,IAAI;QACF,IAAII,OAAO,EAAE;UACX,MAAMA,OAAO,CAACG,OAAO,CAAC;QACxB,CAAC,MAAM,IAAIG,aAAa,EAAE;UACxB,MAAMA,aAAa,CAAC;YAClBM,EAAE,EAAE,EAAE;YACNhB,UAAU;YACVO,OAAO;YACPU,SAAS,EAAEC,IAAI,CAACC,GAAG,CAAC,CAAC;YACrBC,UAAU,EAAE;UACd,CAAC,CAAC;QACJ;QACAnB,OAAO,EAAEoB,mBAAmB,GAAGd,OAAO,CAAC;MACzC,CAAC,CAAC,OAAOe,KAAU,EAAE;QACnBR,OAAO,CAACS,IAAI,CAAC,0BAA0BvB,UAAU,gCAAgC,EAAEsB,KAAK,CAAC;QACzF,MAAMzB,cAAc,CAAC2B,IAAI,CAACxB,UAAU,EAAEO,OAAO,CAAC;QAC9CN,OAAO,EAAEoB,mBAAmB,GAAGd,OAAO,CAAC;QACvCN,OAAO,EAAEwB,OAAO,GAAGH,KAAK,EAAEf,OAAO,CAAC;MACpC;IACF,CAAC,MAAM;MACL;MACA,IAAIM,OAAO,EAAEC,OAAO,CAACC,GAAG,CAAC,0BAA0Bf,UAAU,WAAW,CAAC;MACzE,MAAMH,cAAc,CAAC2B,IAAI,CAACxB,UAAU,EAAEO,OAAO,CAAC;MAC9CN,OAAO,EAAEoB,mBAAmB,GAAGd,OAAO,CAAC;IACzC;EACF,CAAC;EAED,OAAO;IAAEC;EAAc,CAAC;AAC1B","ignoreList":[]}
1
+ {"version":3,"names":["useEffect","useRef","useState","useCallback","OfflineManager","useNetworkStatus","useOfflineMutation","actionName","options","isOnline","handlerRef","handler","current","status","setStatus","error","setError","registerHandler","payload","reset","mutateOffline","getHandler","globalHandler","onSyncAction","hasHandler","__DEV__","console","log","id","createdAt","Date","now","retryCount","onOptimisticSuccess","onSuccess","err","warn","push","onError","isIdle","isLoading","isSuccess","isError","isQueued"],"sourceRoot":"../../../src","sources":["hooks/useOfflineMutation.ts"],"mappings":"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","ignoreList":[]}
@@ -1,8 +1,19 @@
1
+ export type MutationStatus = 'idle' | 'loading' | 'success' | 'error' | 'queued';
2
+ export interface OfflineMutationResult<TPayload> {
3
+ mutateOffline: (payload: TPayload) => Promise<void>;
4
+ status: MutationStatus;
5
+ isIdle: boolean;
6
+ isLoading: boolean;
7
+ isSuccess: boolean;
8
+ isError: boolean;
9
+ isQueued: boolean;
10
+ error: Error | null;
11
+ reset: () => void;
12
+ }
1
13
  export declare function useOfflineMutation<TPayload>(actionName: string, options?: {
2
14
  handler?: (payload: TPayload) => Promise<void>;
3
15
  onOptimisticSuccess?: (payload: TPayload) => void;
4
16
  onError?: (error: Error, payload: TPayload) => void;
5
- }): {
6
- mutateOffline: (payload: TPayload) => Promise<void>;
7
- };
17
+ onSuccess?: (payload: TPayload) => void;
18
+ }): OfflineMutationResult<TPayload>;
8
19
  //# sourceMappingURL=useOfflineMutation.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"useOfflineMutation.d.ts","sourceRoot":"","sources":["../../../src/hooks/useOfflineMutation.ts"],"names":[],"mappings":"AAIA,wBAAgB,kBAAkB,CAAC,QAAQ,EACzC,UAAU,EAAE,MAAM,EAClB,OAAO,CAAC,EAAE;IACR,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,QAAQ,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/C,mBAAmB,CAAC,EAAE,CAAC,OAAO,EAAE,QAAQ,KAAK,IAAI,CAAC;IAClD,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,QAAQ,KAAK,IAAI,CAAC;CACrD;6BAeqC,QAAQ;EAqC/C"}
1
+ {"version":3,"file":"useOfflineMutation.d.ts","sourceRoot":"","sources":["../../../src/hooks/useOfflineMutation.ts"],"names":[],"mappings":"AAIA,MAAM,MAAM,cAAc,GAAG,MAAM,GAAG,SAAS,GAAG,SAAS,GAAG,OAAO,GAAG,QAAQ,CAAC;AAEjF,MAAM,WAAW,qBAAqB,CAAC,QAAQ;IAC7C,aAAa,EAAE,CAAC,OAAO,EAAE,QAAQ,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACpD,MAAM,EAAE,cAAc,CAAC;IACvB,MAAM,EAAE,OAAO,CAAC;IAChB,SAAS,EAAE,OAAO,CAAC;IACnB,SAAS,EAAE,OAAO,CAAC;IACnB,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,EAAE,OAAO,CAAC;IAClB,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;IACpB,KAAK,EAAE,MAAM,IAAI,CAAC;CACnB;AAED,wBAAgB,kBAAkB,CAAC,QAAQ,EACzC,UAAU,EAAE,MAAM,EAClB,OAAO,CAAC,EAAE;IACR,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,QAAQ,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/C,mBAAmB,CAAC,EAAE,CAAC,OAAO,EAAE,QAAQ,KAAK,IAAI,CAAC;IAClD,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,QAAQ,KAAK,IAAI,CAAC;IACpD,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,QAAQ,KAAK,IAAI,CAAC;CACzC,GACA,qBAAqB,CAAC,QAAQ,CAAC,CA8EjC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mustafaaksoy41/react-native-offline-queue",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
4
4
  "description": "A flexible, high-performance offline queue and synchronizer for React Native. Works great with React Query (TanStack Query).",
5
5
  "main": "lib/commonjs/index.js",
6
6
  "module": "lib/module/index.js",
@@ -1,19 +1,37 @@
1
- import { useEffect, useRef } from 'react';
1
+ import { useEffect, useRef, useState, useCallback } from 'react';
2
2
  import { OfflineManager } from '../core/OfflineManager';
3
3
  import { useNetworkStatus } from '../components/OfflineProvider';
4
4
 
5
+ export type MutationStatus = 'idle' | 'loading' | 'success' | 'error' | 'queued';
6
+
7
+ export interface OfflineMutationResult<TPayload> {
8
+ mutateOffline: (payload: TPayload) => Promise<void>;
9
+ status: MutationStatus;
10
+ isIdle: boolean;
11
+ isLoading: boolean;
12
+ isSuccess: boolean;
13
+ isError: boolean;
14
+ isQueued: boolean;
15
+ error: Error | null;
16
+ reset: () => void;
17
+ }
18
+
5
19
  export function useOfflineMutation<TPayload>(
6
20
  actionName: string,
7
21
  options?: {
8
22
  handler?: (payload: TPayload) => Promise<void>;
9
23
  onOptimisticSuccess?: (payload: TPayload) => void;
10
24
  onError?: (error: Error, payload: TPayload) => void;
25
+ onSuccess?: (payload: TPayload) => void;
11
26
  }
12
- ) {
27
+ ): OfflineMutationResult<TPayload> {
13
28
  const { isOnline } = useNetworkStatus();
14
29
  const handlerRef = useRef(options?.handler);
15
30
  handlerRef.current = options?.handler;
16
31
 
32
+ const [status, setStatus] = useState<MutationStatus>('idle');
33
+ const [error, setError] = useState<Error | null>(null);
34
+
17
35
  // Register per-action handler (persists even after unmount)
18
36
  useEffect(() => {
19
37
  if (handlerRef.current) {
@@ -23,7 +41,12 @@ export function useOfflineMutation<TPayload>(
23
41
  }
24
42
  }, [actionName]);
25
43
 
26
- const mutateOffline = async (payload: TPayload) => {
44
+ const reset = useCallback(() => {
45
+ setStatus('idle');
46
+ setError(null);
47
+ }, []);
48
+
49
+ const mutateOffline = useCallback(async (payload: TPayload) => {
27
50
  // Resolve which handler to use: per-action handler > global onSyncAction
28
51
  const handler = handlerRef.current || OfflineManager.getHandler(actionName);
29
52
  const globalHandler = OfflineManager.onSyncAction;
@@ -32,6 +55,8 @@ export function useOfflineMutation<TPayload>(
32
55
  if (isOnline && hasHandler) {
33
56
  // ── ONLINE: Execute directly, skip the queue ──
34
57
  if (__DEV__) console.log(`[OfflineQueue] mutate: ${actionName} (direct)`);
58
+ setStatus('loading');
59
+ setError(null);
35
60
  try {
36
61
  if (handler) {
37
62
  await handler(payload);
@@ -44,20 +69,37 @@ export function useOfflineMutation<TPayload>(
44
69
  retryCount: 0,
45
70
  });
46
71
  }
72
+ setStatus('success');
47
73
  options?.onOptimisticSuccess?.(payload);
48
- } catch (error: any) {
49
- console.warn(`[OfflineQueue] mutate: ${actionName} failed, falling back to queue`, error);
74
+ options?.onSuccess?.(payload);
75
+ } catch (err: any) {
76
+ console.warn(`[OfflineQueue] mutate: ${actionName} failed, falling back to queue`, err);
77
+ // API failed even though online → fallback to queue
50
78
  await OfflineManager.push(actionName, payload);
79
+ setStatus('queued');
80
+ setError(err);
51
81
  options?.onOptimisticSuccess?.(payload);
52
- options?.onError?.(error, payload);
82
+ options?.onError?.(err, payload);
53
83
  }
54
84
  } else {
55
85
  // ── OFFLINE: Add to queue + optimistic update ──
56
86
  if (__DEV__) console.log(`[OfflineQueue] mutate: ${actionName} (queued)`);
57
87
  await OfflineManager.push(actionName, payload);
88
+ setStatus('queued');
89
+ setError(null);
58
90
  options?.onOptimisticSuccess?.(payload);
59
91
  }
60
- };
92
+ }, [actionName, isOnline, options]);
61
93
 
62
- return { mutateOffline };
94
+ return {
95
+ mutateOffline,
96
+ status,
97
+ isIdle: status === 'idle',
98
+ isLoading: status === 'loading',
99
+ isSuccess: status === 'success',
100
+ isError: status === 'error',
101
+ isQueued: status === 'queued',
102
+ error,
103
+ reset,
104
+ };
63
105
  }