@umituz/react-native-tanstack 1.2.8 → 1.2.10
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/react-native-tanstack",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.10",
|
|
4
4
|
"description": "TanStack Query configuration and utilities for React Native apps - Pre-configured QueryClient with AsyncStorage persistence",
|
|
5
5
|
"main": "./src/index.ts",
|
|
6
6
|
"types": "./src/index.ts",
|
|
@@ -1,68 +1,35 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* useOptimisticUpdate Hook
|
|
3
3
|
* Presentation layer - Optimistic update helper
|
|
4
|
-
*
|
|
5
|
-
* General-purpose optimistic updates for any React Native app
|
|
6
4
|
*/
|
|
7
5
|
|
|
8
|
-
import { useMutation, useQueryClient, type
|
|
6
|
+
import { useMutation, useQueryClient, type MutationOptions } from '@tanstack/react-query';
|
|
9
7
|
|
|
10
8
|
/**
|
|
11
9
|
* Optimistic update configuration
|
|
12
10
|
*/
|
|
13
11
|
export interface OptimisticUpdateConfig<TData, TVariables> {
|
|
14
|
-
/**
|
|
15
|
-
* Query key to update optimistically
|
|
16
|
-
*/
|
|
17
12
|
queryKey: readonly unknown[];
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Function to update the cached data optimistically
|
|
21
|
-
*/
|
|
22
13
|
updater: (oldData: TData | undefined, variables: TVariables) => TData;
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Whether to invalidate the query after successful mutation
|
|
26
|
-
* @default true
|
|
27
|
-
*/
|
|
28
14
|
invalidateOnSuccess?: boolean;
|
|
29
15
|
}
|
|
30
16
|
|
|
31
17
|
/**
|
|
32
18
|
* Hook for mutations with optimistic updates and automatic rollback
|
|
33
|
-
*
|
|
34
|
-
* @example
|
|
35
|
-
* ```typescript
|
|
36
|
-
* const updatePost = useOptimisticUpdate<Post, UpdatePostVariables>({
|
|
37
|
-
* mutationFn: (variables) => api.updatePost(variables.id, variables.data),
|
|
38
|
-
* queryKey: ['posts', postId],
|
|
39
|
-
* updater: (oldPost, variables) => ({
|
|
40
|
-
* ...oldPost,
|
|
41
|
-
* ...variables.data,
|
|
42
|
-
* }),
|
|
43
|
-
* });
|
|
44
|
-
*
|
|
45
|
-
* // Usage
|
|
46
|
-
* updatePost.mutate({ id: 123, data: { title: 'New Title' } });
|
|
47
|
-
* ```
|
|
48
19
|
*/
|
|
49
20
|
export function useOptimisticUpdate<TData = unknown, TVariables = unknown, TError = Error>(
|
|
50
21
|
config: OptimisticUpdateConfig<TData, TVariables> &
|
|
51
|
-
|
|
22
|
+
MutationOptions<TData, TError, TVariables>,
|
|
52
23
|
) {
|
|
53
24
|
const queryClient = useQueryClient();
|
|
54
25
|
const { queryKey, updater, invalidateOnSuccess = true, onError, onSettled, ...mutationOptions } = config;
|
|
55
26
|
|
|
56
27
|
return useMutation({
|
|
57
28
|
...mutationOptions,
|
|
58
|
-
onMutate: async (variables) => {
|
|
59
|
-
// Cancel outgoing refetches to avoid overwriting optimistic update
|
|
29
|
+
onMutate: async (variables: TVariables) => {
|
|
60
30
|
await queryClient.cancelQueries({ queryKey });
|
|
61
|
-
|
|
62
|
-
// Snapshot the previous value
|
|
63
31
|
const previousData = queryClient.getQueryData<TData>(queryKey);
|
|
64
32
|
|
|
65
|
-
// Optimistically update to the new value
|
|
66
33
|
if (previousData !== undefined) {
|
|
67
34
|
const optimisticData = updater(previousData, variables);
|
|
68
35
|
queryClient.setQueryData(queryKey, optimisticData);
|
|
@@ -73,11 +40,10 @@ export function useOptimisticUpdate<TData = unknown, TVariables = unknown, TErro
|
|
|
73
40
|
}
|
|
74
41
|
}
|
|
75
42
|
|
|
76
|
-
// Return context with previous data for rollback
|
|
77
43
|
return { previousData };
|
|
78
44
|
},
|
|
79
|
-
|
|
80
|
-
|
|
45
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
46
|
+
onError: (error: TError, variables: TVariables, context: any) => {
|
|
81
47
|
if (context?.previousData !== undefined) {
|
|
82
48
|
queryClient.setQueryData(queryKey, context.previousData);
|
|
83
49
|
|
|
@@ -87,40 +53,29 @@ export function useOptimisticUpdate<TData = unknown, TVariables = unknown, TErro
|
|
|
87
53
|
}
|
|
88
54
|
}
|
|
89
55
|
|
|
90
|
-
// Call user-provided onError
|
|
91
56
|
if (onError) {
|
|
92
|
-
onError(error, variables, context
|
|
57
|
+
onError(error, variables, context);
|
|
93
58
|
}
|
|
94
59
|
},
|
|
95
|
-
|
|
96
|
-
|
|
60
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
61
|
+
onSettled: (data: TData | undefined, error: TError | null, variables: TVariables, context: any) => {
|
|
97
62
|
if (invalidateOnSuccess && !error) {
|
|
98
63
|
queryClient.invalidateQueries({ queryKey });
|
|
99
64
|
}
|
|
100
65
|
|
|
101
|
-
// Call user-provided onSettled
|
|
102
66
|
if (onSettled) {
|
|
103
|
-
onSettled(data, error, variables, context
|
|
67
|
+
onSettled(data, error, variables, context);
|
|
104
68
|
}
|
|
105
69
|
},
|
|
106
70
|
});
|
|
107
71
|
}
|
|
108
72
|
|
|
109
73
|
/**
|
|
110
|
-
* Hook for list mutations with optimistic updates
|
|
111
|
-
*
|
|
112
|
-
* @example
|
|
113
|
-
* ```typescript
|
|
114
|
-
* const addPost = useOptimisticListUpdate<Post[], { title: string }>({
|
|
115
|
-
* mutationFn: (variables) => api.createPost(variables),
|
|
116
|
-
* queryKey: ['posts'],
|
|
117
|
-
* updater: (oldPosts, newPost) => [...(oldPosts ?? []), newPost],
|
|
118
|
-
* });
|
|
119
|
-
* ```
|
|
74
|
+
* Hook for list mutations with optimistic updates
|
|
120
75
|
*/
|
|
121
76
|
export function useOptimisticListUpdate<TData extends unknown[], TVariables = unknown>(
|
|
122
77
|
config: OptimisticUpdateConfig<TData, TVariables> &
|
|
123
|
-
|
|
78
|
+
MutationOptions<TData, Error, TVariables>,
|
|
124
79
|
) {
|
|
125
80
|
return useOptimisticUpdate<TData, TVariables>(config);
|
|
126
81
|
}
|
|
@@ -1,15 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* usePaginatedQuery Hook
|
|
3
3
|
* Presentation layer - Pagination helper
|
|
4
|
-
*
|
|
5
|
-
* General-purpose pagination for any React Native app
|
|
6
4
|
*/
|
|
7
5
|
|
|
8
|
-
import {
|
|
9
|
-
useInfiniteQuery,
|
|
10
|
-
type UseInfiniteQueryOptions,
|
|
11
|
-
type InfiniteData,
|
|
12
|
-
} from '@tanstack/react-query';
|
|
6
|
+
import { useInfiniteQuery } from '@tanstack/react-query';
|
|
13
7
|
import { useMemo } from 'react';
|
|
14
8
|
|
|
15
9
|
/**
|
|
@@ -47,49 +41,36 @@ export interface OffsetPaginatedResponse<TData> {
|
|
|
47
41
|
limit: number;
|
|
48
42
|
}
|
|
49
43
|
|
|
44
|
+
/**
|
|
45
|
+
* Cursor pagination options
|
|
46
|
+
*/
|
|
47
|
+
export interface CursorPaginationOptions<TData> {
|
|
48
|
+
queryKey: readonly unknown[];
|
|
49
|
+
queryFn: (context: { pageParam?: string }) => Promise<CursorPaginatedResponse<TData>>;
|
|
50
|
+
limit?: number;
|
|
51
|
+
enabled?: boolean;
|
|
52
|
+
staleTime?: number;
|
|
53
|
+
gcTime?: number;
|
|
54
|
+
}
|
|
55
|
+
|
|
50
56
|
/**
|
|
51
57
|
* Hook for cursor-based infinite scroll
|
|
52
|
-
*
|
|
53
|
-
* @example
|
|
54
|
-
* ```typescript
|
|
55
|
-
* const { data, fetchNextPage, hasNextPage, isFetchingNextPage } = useCursorPagination({
|
|
56
|
-
* queryKey: ['posts'],
|
|
57
|
-
* queryFn: ({ pageParam }) => fetchPosts({ cursor: pageParam, limit: 20 }),
|
|
58
|
-
* limit: 20,
|
|
59
|
-
* });
|
|
60
|
-
*
|
|
61
|
-
* // Flatten all pages into single array
|
|
62
|
-
* const allPosts = data.pages.flatMap(page => page.items);
|
|
63
|
-
* ```
|
|
64
58
|
*/
|
|
65
|
-
export function useCursorPagination<TData>(
|
|
66
|
-
|
|
67
|
-
UseInfiniteQueryOptions<
|
|
68
|
-
CursorPaginatedResponse<TData>,
|
|
69
|
-
Error,
|
|
70
|
-
CursorPaginatedResponse<TData>,
|
|
71
|
-
readonly unknown[],
|
|
72
|
-
string | undefined
|
|
73
|
-
>,
|
|
74
|
-
'getNextPageParam' | 'initialPageParam'
|
|
75
|
-
> & {
|
|
76
|
-
limit?: number;
|
|
77
|
-
},
|
|
78
|
-
) {
|
|
79
|
-
const { limit = 20, ...queryOptions } = options;
|
|
59
|
+
export function useCursorPagination<TData>(options: CursorPaginationOptions<TData>) {
|
|
60
|
+
const { queryKey, queryFn, limit: _limit = 20, ...restOptions } = options;
|
|
80
61
|
|
|
81
62
|
const result = useInfiniteQuery({
|
|
82
|
-
|
|
83
|
-
|
|
63
|
+
queryKey,
|
|
64
|
+
queryFn: ({ pageParam }) => queryFn({ pageParam }),
|
|
65
|
+
initialPageParam: undefined as string | undefined,
|
|
84
66
|
getNextPageParam: (lastPage: CursorPaginatedResponse<TData>) =>
|
|
85
67
|
lastPage.hasMore ? lastPage.nextCursor : undefined,
|
|
68
|
+
...restOptions,
|
|
86
69
|
});
|
|
87
70
|
|
|
88
|
-
// Flatten pages into single array for easier consumption
|
|
89
71
|
const flatData = useMemo(() => {
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
return infiniteData.pages.flatMap((page) => page.items);
|
|
72
|
+
if (!result.data?.pages) return [];
|
|
73
|
+
return result.data.pages.flatMap((page) => page.items);
|
|
93
74
|
}, [result.data]);
|
|
94
75
|
|
|
95
76
|
return {
|
|
@@ -99,53 +80,41 @@ export function useCursorPagination<TData>(
|
|
|
99
80
|
};
|
|
100
81
|
}
|
|
101
82
|
|
|
83
|
+
/**
|
|
84
|
+
* Offset pagination options
|
|
85
|
+
*/
|
|
86
|
+
export interface OffsetPaginationOptions<TData> {
|
|
87
|
+
queryKey: readonly unknown[];
|
|
88
|
+
queryFn: (context: { pageParam: OffsetPageParam }) => Promise<OffsetPaginatedResponse<TData>>;
|
|
89
|
+
limit?: number;
|
|
90
|
+
enabled?: boolean;
|
|
91
|
+
staleTime?: number;
|
|
92
|
+
gcTime?: number;
|
|
93
|
+
}
|
|
94
|
+
|
|
102
95
|
/**
|
|
103
96
|
* Hook for offset-based pagination
|
|
104
|
-
*
|
|
105
|
-
* @example
|
|
106
|
-
* ```typescript
|
|
107
|
-
* const { data, fetchNextPage, hasNextPage } = useOffsetPagination({
|
|
108
|
-
* queryKey: ['posts'],
|
|
109
|
-
* queryFn: ({ pageParam }) => fetchPosts({ offset: pageParam.offset, limit: pageParam.limit }),
|
|
110
|
-
* limit: 20,
|
|
111
|
-
* });
|
|
112
|
-
* ```
|
|
113
97
|
*/
|
|
114
|
-
export function useOffsetPagination<TData>(
|
|
115
|
-
options
|
|
116
|
-
UseInfiniteQueryOptions<
|
|
117
|
-
OffsetPaginatedResponse<TData>,
|
|
118
|
-
Error,
|
|
119
|
-
OffsetPaginatedResponse<TData>,
|
|
120
|
-
readonly unknown[],
|
|
121
|
-
OffsetPageParam
|
|
122
|
-
>,
|
|
123
|
-
'getNextPageParam' | 'initialPageParam'
|
|
124
|
-
> & {
|
|
125
|
-
limit?: number;
|
|
126
|
-
},
|
|
127
|
-
) {
|
|
128
|
-
const { limit = 20, ...queryOptions } = options;
|
|
98
|
+
export function useOffsetPagination<TData>(options: OffsetPaginationOptions<TData>) {
|
|
99
|
+
const { queryKey, queryFn, limit = 20, ...restOptions } = options;
|
|
129
100
|
|
|
130
101
|
const result = useInfiniteQuery({
|
|
131
|
-
|
|
102
|
+
queryKey,
|
|
103
|
+
queryFn: ({ pageParam }) => queryFn({ pageParam }),
|
|
132
104
|
initialPageParam: { offset: 0, limit },
|
|
133
105
|
getNextPageParam: (lastPage: OffsetPaginatedResponse<TData>) => {
|
|
134
106
|
const nextOffset = lastPage.offset + lastPage.limit;
|
|
135
107
|
return nextOffset < lastPage.total ? { offset: nextOffset, limit } : undefined;
|
|
136
108
|
},
|
|
109
|
+
...restOptions,
|
|
137
110
|
});
|
|
138
111
|
|
|
139
|
-
// Flatten pages into single array
|
|
140
112
|
const flatData = useMemo(() => {
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
return infiniteData.pages.flatMap((page) => page.items);
|
|
113
|
+
if (!result.data?.pages) return [];
|
|
114
|
+
return result.data.pages.flatMap((page) => page.items);
|
|
144
115
|
}, [result.data]);
|
|
145
116
|
|
|
146
|
-
|
|
147
|
-
const infiniteData = result.data as InfiniteData<OffsetPaginatedResponse<TData>> | undefined;
|
|
148
|
-
const total = infiniteData?.pages?.[infiniteData.pages.length - 1]?.total ?? 0;
|
|
117
|
+
const total = result.data?.pages?.[result.data.pages.length - 1]?.total ?? 0;
|
|
149
118
|
|
|
150
119
|
return {
|
|
151
120
|
...result,
|