@umituz/react-native-tanstack 1.0.0

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 ADDED
@@ -0,0 +1,206 @@
1
+ # @umituz/react-native-tanstack
2
+
3
+ TanStack Query configuration and utilities for React Native apps with AsyncStorage persistence.
4
+
5
+ ## Features
6
+
7
+ - ✅ **Pre-configured QueryClient** - Sensible defaults out of the box
8
+ - ✅ **AsyncStorage Persistence** - Automatic cache restoration on app restart
9
+ - ✅ **Cache Strategies** - Pre-defined strategies for different data types
10
+ - ✅ **Query Key Factories** - Type-safe key generation patterns
11
+ - ✅ **Pagination Helpers** - Cursor and offset-based pagination
12
+ - ✅ **Optimistic Updates** - Easy optimistic UI with automatic rollback
13
+ - ✅ **Dev Tools** - Built-in logging for development
14
+ - ✅ **General Purpose** - Works with Firebase, REST, GraphQL, any async data source
15
+
16
+ ## Installation
17
+
18
+ ```bash
19
+ npm install @umituz/react-native-tanstack
20
+ ```
21
+
22
+ ### Peer Dependencies
23
+
24
+ ```bash
25
+ npm install @tanstack/react-query @react-native-async-storage/async-storage
26
+ ```
27
+
28
+ ## Usage
29
+
30
+ ### Basic Setup
31
+
32
+ ```typescript
33
+ import { TanstackProvider } from '@umituz/react-native-tanstack';
34
+
35
+ function App() {
36
+ return (
37
+ <TanstackProvider>
38
+ <YourApp />
39
+ </TanstackProvider>
40
+ );
41
+ }
42
+ ```
43
+
44
+ ### Custom Configuration
45
+
46
+ ```typescript
47
+ import { TanstackProvider, TIME_MS } from '@umituz/react-native-tanstack';
48
+
49
+ function App() {
50
+ return (
51
+ <TanstackProvider
52
+ queryClientOptions={{
53
+ defaultStaleTime: 10 * TIME_MS.MINUTE,
54
+ enableDevLogging: __DEV__,
55
+ }}
56
+ persisterOptions={{
57
+ keyPrefix: 'myapp',
58
+ maxAge: 24 * TIME_MS.HOUR,
59
+ busterVersion: '1',
60
+ }}
61
+ onPersistSuccess={() => console.log('Cache restored!')}
62
+ >
63
+ <YourApp />
64
+ </TanstackProvider>
65
+ );
66
+ }
67
+ ```
68
+
69
+ ### Cache Strategies
70
+
71
+ ```typescript
72
+ import { useQuery, CacheStrategies } from '@umituz/react-native-tanstack';
73
+
74
+ // Real-time data (always refetch)
75
+ const { data: liveScore } = useQuery({
76
+ queryKey: ['score'],
77
+ queryFn: fetchScore,
78
+ ...CacheStrategies.REALTIME,
79
+ });
80
+
81
+ // User data (medium cache)
82
+ const { data: profile } = useQuery({
83
+ queryKey: ['profile'],
84
+ queryFn: fetchProfile,
85
+ ...CacheStrategies.USER_DATA,
86
+ });
87
+
88
+ // Master data (long cache)
89
+ const { data: countries } = useQuery({
90
+ queryKey: ['countries'],
91
+ queryFn: fetchCountries,
92
+ ...CacheStrategies.MASTER_DATA,
93
+ });
94
+
95
+ // Public data (medium-long cache)
96
+ const { data: posts } = useQuery({
97
+ queryKey: ['posts'],
98
+ queryFn: fetchPosts,
99
+ ...CacheStrategies.PUBLIC_DATA,
100
+ });
101
+ ```
102
+
103
+ ### Query Key Factories
104
+
105
+ ```typescript
106
+ import { createQueryKeyFactory } from '@umituz/react-native-tanstack';
107
+
108
+ const postKeys = createQueryKeyFactory('posts');
109
+
110
+ // All posts
111
+ postKeys.all(); // ['posts']
112
+
113
+ // Posts list
114
+ postKeys.lists(); // ['posts', 'list']
115
+
116
+ // Posts with filters
117
+ postKeys.list({ status: 'published' }); // ['posts', 'list', { status: 'published' }]
118
+
119
+ // Single post
120
+ postKeys.detail(123); // ['posts', 'detail', 123]
121
+
122
+ // Custom key
123
+ postKeys.custom('trending'); // ['posts', 'trending']
124
+ ```
125
+
126
+ ### Pagination
127
+
128
+ ```typescript
129
+ import { useCursorPagination } from '@umituz/react-native-tanstack';
130
+
131
+ function FeedScreen() {
132
+ const { data, flatData, fetchNextPage, hasNextPage, isFetchingNextPage } = useCursorPagination({
133
+ queryKey: ['feed'],
134
+ queryFn: ({ pageParam }) => fetchFeed({ cursor: pageParam, limit: 20 }),
135
+ limit: 20,
136
+ });
137
+
138
+ return (
139
+ <FlatList
140
+ data={flatData}
141
+ onEndReached={() => hasNextPage && fetchNextPage()}
142
+ onEndReachedThreshold={0.5}
143
+ />
144
+ );
145
+ }
146
+ ```
147
+
148
+ ### Cache Invalidation
149
+
150
+ ```typescript
151
+ import { useInvalidateQueries } from '@umituz/react-native-tanstack';
152
+
153
+ function ShareButton() {
154
+ const invalidate = useInvalidateQueries();
155
+
156
+ const handleShare = async () => {
157
+ await shareToFeed(post);
158
+
159
+ // Invalidate feed queries
160
+ await invalidate(['feed']);
161
+ };
162
+
163
+ return <Button onPress={handleShare}>Share</Button>;
164
+ }
165
+ ```
166
+
167
+ ### Optimistic Updates
168
+
169
+ ```typescript
170
+ import { useOptimisticUpdate } from '@umituz/react-native-tanstack';
171
+
172
+ function LikeButton({ postId }) {
173
+ const updateLike = useOptimisticUpdate<Post, { liked: boolean }>({
174
+ mutationFn: (variables) => api.updatePost(postId, variables),
175
+ queryKey: ['posts', 'detail', postId],
176
+ updater: (oldPost, variables) => ({
177
+ ...oldPost,
178
+ liked: variables.liked,
179
+ likeCount: oldPost.likeCount + (variables.liked ? 1 : -1),
180
+ }),
181
+ });
182
+
183
+ const handleLike = () => {
184
+ updateLike.mutate({ liked: true });
185
+ };
186
+
187
+ return <Button onPress={handleLike}>Like</Button>;
188
+ }
189
+ ```
190
+
191
+ ## Cache Strategies
192
+
193
+ | Strategy | staleTime | gcTime | Use Case |
194
+ |----------|-----------|--------|----------|
195
+ | REALTIME | 0 | 5 min | Live data (chat, scores) |
196
+ | USER_DATA | 30 min | 24 hours | User profile, settings |
197
+ | MASTER_DATA | 24 hours | 7 days | Countries, categories |
198
+ | PUBLIC_DATA | 30 min | 24 hours | Feed, blog posts |
199
+
200
+ ## API Reference
201
+
202
+ See [TypeScript definitions](./src/index.ts) for complete API documentation.
203
+
204
+ ## License
205
+
206
+ MIT © Ümit UZ
package/package.json ADDED
@@ -0,0 +1,58 @@
1
+ {
2
+ "name": "@umituz/react-native-tanstack",
3
+ "version": "1.0.0",
4
+ "description": "TanStack Query configuration and utilities for React Native apps - Pre-configured QueryClient with AsyncStorage persistence",
5
+ "main": "./src/index.ts",
6
+ "types": "./src/index.ts",
7
+ "scripts": {
8
+ "typecheck": "tsc --noEmit",
9
+ "lint": "tsc --noEmit",
10
+ "version:patch": "npm version patch -m 'chore: release v%s'",
11
+ "version:minor": "npm version minor -m 'chore: release v%s'",
12
+ "version:major": "npm version major -m 'chore: release v%s'"
13
+ },
14
+ "keywords": [
15
+ "react-native",
16
+ "tanstack-query",
17
+ "react-query",
18
+ "cache",
19
+ "async-storage",
20
+ "persistence",
21
+ "query-client",
22
+ "data-fetching",
23
+ "server-state"
24
+ ],
25
+ "author": "Ümit UZ <umit@umituz.com>",
26
+ "license": "MIT",
27
+ "repository": {
28
+ "type": "git",
29
+ "url": "git+https://github.com/umituz/react-native-tanstack.git"
30
+ },
31
+ "peerDependencies": {
32
+ "@react-native-async-storage/async-storage": ">=1.21.0",
33
+ "@tanstack/react-query": "^5.0.0",
34
+ "react": ">=18.2.0",
35
+ "react-native": ">=0.74.0"
36
+ },
37
+ "dependencies": {
38
+ "@tanstack/query-async-storage-persister": "^5.62.0",
39
+ "@tanstack/react-query-persist-client": "^5.62.0"
40
+ },
41
+ "devDependencies": {
42
+ "@react-native-async-storage/async-storage": "^1.24.0",
43
+ "@tanstack/react-query": "^5.62.11",
44
+ "@types/react": "^18.2.45",
45
+ "@types/react-native": "^0.73.0",
46
+ "react": "^18.2.0",
47
+ "react-native": "^0.74.0",
48
+ "typescript": "^5.6.0"
49
+ },
50
+ "publishConfig": {
51
+ "access": "public"
52
+ },
53
+ "files": [
54
+ "src",
55
+ "README.md",
56
+ "LICENSE"
57
+ ]
58
+ }
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Cache Time Constants
3
+ * Domain layer - Time constants for cache management
4
+ *
5
+ * General-purpose time utilities for any React Native app
6
+ */
7
+
8
+ /**
9
+ * Milliseconds constants for time calculations
10
+ */
11
+ export const TIME_MS = {
12
+ SECOND: 1000,
13
+ MINUTE: 60 * 1000,
14
+ HOUR: 60 * 60 * 1000,
15
+ DAY: 24 * 60 * 60 * 1000,
16
+ WEEK: 7 * 24 * 60 * 60 * 1000,
17
+ } as const;
18
+
19
+ /**
20
+ * Default staleTime values for different cache strategies
21
+ * staleTime = how long data is considered fresh
22
+ */
23
+ export const DEFAULT_STALE_TIME = {
24
+ REALTIME: 0, // Always stale (refetch immediately)
25
+ VERY_SHORT: TIME_MS.MINUTE, // 1 minute
26
+ SHORT: 5 * TIME_MS.MINUTE, // 5 minutes
27
+ MEDIUM: 30 * TIME_MS.MINUTE, // 30 minutes
28
+ LONG: 2 * TIME_MS.HOUR, // 2 hours
29
+ VERY_LONG: TIME_MS.DAY, // 24 hours
30
+ PERMANENT: Infinity, // Never stale
31
+ } as const;
32
+
33
+ /**
34
+ * Default gcTime (garbage collection) values
35
+ * gcTime = how long unused data stays in cache before being garbage collected
36
+ */
37
+ export const DEFAULT_GC_TIME = {
38
+ VERY_SHORT: 5 * TIME_MS.MINUTE, // 5 minutes
39
+ SHORT: 30 * TIME_MS.MINUTE, // 30 minutes
40
+ MEDIUM: 2 * TIME_MS.HOUR, // 2 hours
41
+ LONG: TIME_MS.DAY, // 24 hours
42
+ VERY_LONG: 7 * TIME_MS.DAY, // 7 days
43
+ } as const;
44
+
45
+ /**
46
+ * Default retry configuration
47
+ */
48
+ export const DEFAULT_RETRY = {
49
+ NONE: false,
50
+ MINIMAL: 1,
51
+ STANDARD: 3,
52
+ AGGRESSIVE: 5,
53
+ } as const;
54
+
55
+ /**
56
+ * Default refetch intervals
57
+ */
58
+ export const DEFAULT_REFETCH_INTERVAL = {
59
+ REALTIME: 10 * TIME_MS.SECOND, // 10 seconds
60
+ FAST: 30 * TIME_MS.SECOND, // 30 seconds
61
+ MODERATE: TIME_MS.MINUTE, // 1 minute
62
+ SLOW: 5 * TIME_MS.MINUTE, // 5 minutes
63
+ } as const;
@@ -0,0 +1,115 @@
1
+ /**
2
+ * Cache Strategy Types
3
+ * Domain layer - Cache configuration types
4
+ *
5
+ * General-purpose cache strategies for any React Native app
6
+ */
7
+
8
+ import type { UseQueryOptions } from '@tanstack/react-query';
9
+
10
+ /**
11
+ * Cache configuration for TanStack Query
12
+ */
13
+ export interface CacheConfig {
14
+ /**
15
+ * Time in ms data is considered fresh (no refetch)
16
+ */
17
+ staleTime: number;
18
+
19
+ /**
20
+ * Time in ms inactive data stays in cache before garbage collection
21
+ */
22
+ gcTime: number;
23
+
24
+ /**
25
+ * Whether to refetch when component mounts
26
+ */
27
+ refetchOnMount?: boolean | 'always';
28
+
29
+ /**
30
+ * Whether to refetch when window regains focus
31
+ */
32
+ refetchOnWindowFocus?: boolean | 'always';
33
+
34
+ /**
35
+ * Whether to refetch when network reconnects
36
+ */
37
+ refetchOnReconnect?: boolean | 'always';
38
+
39
+ /**
40
+ * Number of retry attempts on failure
41
+ */
42
+ retry?: boolean | number;
43
+
44
+ /**
45
+ * Interval for automatic background refetching (in ms)
46
+ * Set to false to disable
47
+ */
48
+ refetchInterval?: number | false;
49
+ }
50
+
51
+ /**
52
+ * Cache strategy enum for different data types
53
+ */
54
+ export enum CacheStrategyType {
55
+ /**
56
+ * Real-time data that changes frequently
57
+ * Example: Live chat, stock prices, sports scores
58
+ */
59
+ REALTIME = 'REALTIME',
60
+
61
+ /**
62
+ * User-specific data
63
+ * Example: User profile, settings, preferences
64
+ */
65
+ USER_DATA = 'USER_DATA',
66
+
67
+ /**
68
+ * Master data that rarely changes
69
+ * Example: Countries list, categories, app configuration
70
+ */
71
+ MASTER_DATA = 'MASTER_DATA',
72
+
73
+ /**
74
+ * Public read-heavy data
75
+ * Example: Blog posts, product catalog, news feed
76
+ */
77
+ PUBLIC_DATA = 'PUBLIC_DATA',
78
+
79
+ /**
80
+ * Custom strategy (user-defined)
81
+ */
82
+ CUSTOM = 'CUSTOM',
83
+ }
84
+
85
+ /**
86
+ * Query options type (generic, works with any data)
87
+ */
88
+ export type QueryConfig<TData = unknown, TError = Error> = Partial<
89
+ UseQueryOptions<TData, TError>
90
+ >;
91
+
92
+ /**
93
+ * Mutation options type (generic, works with any data)
94
+ */
95
+ export interface MutationConfig<TData = unknown, TError = Error, TVariables = unknown> {
96
+ /**
97
+ * Function to call on mutation success
98
+ */
99
+ onSuccess?: (data: TData, variables: TVariables) => void | Promise<void>;
100
+
101
+ /**
102
+ * Function to call on mutation error
103
+ */
104
+ onError?: (error: TError, variables: TVariables) => void | Promise<void>;
105
+
106
+ /**
107
+ * Function to call on mutation settled (success or error)
108
+ */
109
+ onSettled?: (data: TData | undefined, error: TError | null, variables: TVariables) => void | Promise<void>;
110
+
111
+ /**
112
+ * Number of retry attempts
113
+ */
114
+ retry?: boolean | number;
115
+ }
@@ -0,0 +1,134 @@
1
+ /**
2
+ * Query Key Factory
3
+ * Domain layer - Query key generation utilities
4
+ *
5
+ * General-purpose query key patterns for any React Native app
6
+ */
7
+
8
+ /**
9
+ * Create a typed query key factory for a resource
10
+ *
11
+ * @example
12
+ * ```typescript
13
+ * const postKeys = createQueryKeyFactory('posts');
14
+ *
15
+ * // All posts
16
+ * postKeys.all() // ['posts']
17
+ *
18
+ * // Posts list
19
+ * postKeys.lists() // ['posts', 'list']
20
+ *
21
+ * // Posts list with filters
22
+ * postKeys.list({ status: 'published' }) // ['posts', 'list', { status: 'published' }]
23
+ *
24
+ * // Single post detail
25
+ * postKeys.detail(123) // ['posts', 'detail', 123]
26
+ * ```
27
+ */
28
+ export function createQueryKeyFactory(resource: string) {
29
+ return {
30
+ /**
31
+ * All queries for this resource
32
+ */
33
+ all: () => [resource] as const,
34
+
35
+ /**
36
+ * All list queries for this resource
37
+ */
38
+ lists: () => [resource, 'list'] as const,
39
+
40
+ /**
41
+ * List query with optional filters
42
+ */
43
+ list: (filters?: Record<string, unknown>) =>
44
+ filters ? ([resource, 'list', filters] as const) : ([resource, 'list'] as const),
45
+
46
+ /**
47
+ * All detail queries for this resource
48
+ */
49
+ details: () => [resource, 'detail'] as const,
50
+
51
+ /**
52
+ * Detail query for specific item
53
+ */
54
+ detail: (id: string | number) => [resource, 'detail', id] as const,
55
+
56
+ /**
57
+ * Custom query key
58
+ */
59
+ custom: (...args: unknown[]) => [resource, ...args] as const,
60
+ };
61
+ }
62
+
63
+ /**
64
+ * Create a query key for a list with pagination
65
+ *
66
+ * @example
67
+ * ```typescript
68
+ * createPaginatedQueryKey('posts', { page: 1, limit: 10 })
69
+ * // ['posts', 'list', { page: 1, limit: 10 }]
70
+ * ```
71
+ */
72
+ export function createPaginatedQueryKey(
73
+ resource: string,
74
+ pagination: { page?: number; limit?: number; cursor?: string },
75
+ ): readonly [string, 'list', typeof pagination] {
76
+ return [resource, 'list', pagination] as const;
77
+ }
78
+
79
+ /**
80
+ * Create a query key for infinite scroll
81
+ *
82
+ * @example
83
+ * ```typescript
84
+ * createInfiniteQueryKey('feed', { limit: 20 })
85
+ * // ['feed', 'infinite', { limit: 20 }]
86
+ * ```
87
+ */
88
+ export function createInfiniteQueryKey(
89
+ resource: string,
90
+ params?: Record<string, unknown>,
91
+ ): readonly [string, 'infinite', Record<string, unknown>] | readonly [string, 'infinite'] {
92
+ return params ? ([resource, 'infinite', params] as const) : ([resource, 'infinite'] as const);
93
+ }
94
+
95
+ /**
96
+ * Create a scoped query key (for user-specific data)
97
+ *
98
+ * @example
99
+ * ```typescript
100
+ * createScopedQueryKey('user123', 'posts')
101
+ * // ['user', 'user123', 'posts']
102
+ * ```
103
+ */
104
+ export function createScopedQueryKey(
105
+ scopeId: string,
106
+ resource: string,
107
+ ...args: unknown[]
108
+ ): readonly [string, string, string, ...unknown[]] {
109
+ return ['user', scopeId, resource, ...args] as const;
110
+ }
111
+
112
+ /**
113
+ * Match query keys by pattern
114
+ * Useful for invalidating multiple related queries
115
+ *
116
+ * @example
117
+ * ```typescript
118
+ * // Invalidate all post queries
119
+ * queryClient.invalidateQueries({
120
+ * predicate: (query) => matchQueryKey(query.queryKey, ['posts'])
121
+ * })
122
+ * ```
123
+ */
124
+ export function matchQueryKey(
125
+ queryKey: readonly unknown[],
126
+ pattern: readonly unknown[],
127
+ ): boolean {
128
+ if (pattern.length > queryKey.length) return false;
129
+
130
+ return pattern.every((value, index) => {
131
+ if (value === undefined) return true;
132
+ return queryKey[index] === value;
133
+ });
134
+ }
package/src/index.ts ADDED
@@ -0,0 +1,88 @@
1
+ /**
2
+ * @umituz/react-native-tanstack
3
+ * TanStack Query configuration and utilities for React Native apps
4
+ *
5
+ * General-purpose package for hundreds of React Native apps
6
+ */
7
+
8
+ // Domain - Constants
9
+ export {
10
+ TIME_MS,
11
+ DEFAULT_STALE_TIME,
12
+ DEFAULT_GC_TIME,
13
+ DEFAULT_RETRY,
14
+ DEFAULT_REFETCH_INTERVAL,
15
+ } from './domain/constants/CacheDefaults';
16
+
17
+ // Domain - Types
18
+ export {
19
+ CacheStrategyType,
20
+ type CacheConfig,
21
+ type QueryConfig,
22
+ type MutationConfig,
23
+ } from './domain/types/CacheStrategy';
24
+
25
+ // Domain - Utils
26
+ export {
27
+ createQueryKeyFactory,
28
+ createPaginatedQueryKey,
29
+ createInfiniteQueryKey,
30
+ createScopedQueryKey,
31
+ matchQueryKey,
32
+ } from './domain/utils/QueryKeyFactory';
33
+
34
+ // Infrastructure - Config
35
+ export {
36
+ CacheStrategies,
37
+ createQueryClient,
38
+ getCacheStrategy,
39
+ type QueryClientFactoryOptions,
40
+ } from './infrastructure/config/QueryClientConfig';
41
+
42
+ export {
43
+ createPersister,
44
+ clearPersistedCache,
45
+ getPersistedCacheSize,
46
+ type PersisterFactoryOptions,
47
+ } from './infrastructure/config/PersisterConfig';
48
+
49
+ // Infrastructure - Providers
50
+ export { TanstackProvider, type TanstackProviderProps } from './infrastructure/providers/TanstackProvider';
51
+
52
+ // Presentation - Hooks
53
+ export {
54
+ useInvalidateQueries,
55
+ useInvalidateMultipleQueries,
56
+ useRemoveQueries,
57
+ useResetQueries,
58
+ } from './presentation/hooks/useInvalidateQueries';
59
+
60
+ export {
61
+ useCursorPagination,
62
+ useOffsetPagination,
63
+ type CursorPageParam,
64
+ type OffsetPageParam,
65
+ type CursorPaginatedResponse,
66
+ type OffsetPaginatedResponse,
67
+ } from './presentation/hooks/usePaginatedQuery';
68
+
69
+ export {
70
+ useOptimisticUpdate,
71
+ useOptimisticListUpdate,
72
+ type OptimisticUpdateConfig,
73
+ } from './presentation/hooks/useOptimisticUpdate';
74
+
75
+ // Re-export TanStack Query core for convenience
76
+ export {
77
+ useQuery,
78
+ useMutation,
79
+ useInfiniteQuery,
80
+ useQueryClient,
81
+ useIsFetching,
82
+ useIsMutating,
83
+ type UseQueryResult,
84
+ type UseMutationResult,
85
+ type UseInfiniteQueryResult,
86
+ type QueryKey,
87
+ type QueryClient,
88
+ } from '@tanstack/react-query';