@croacroa/react-native-template 1.0.0 → 2.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.
Files changed (69) hide show
  1. package/.github/workflows/ci.yml +187 -184
  2. package/.github/workflows/eas-build.yml +55 -55
  3. package/.github/workflows/eas-update.yml +50 -50
  4. package/CHANGELOG.md +106 -106
  5. package/CONTRIBUTING.md +377 -377
  6. package/README.md +399 -399
  7. package/__tests__/components/snapshots.test.tsx +131 -0
  8. package/__tests__/integration/auth-api.test.tsx +227 -0
  9. package/__tests__/performance/VirtualizedList.perf.test.tsx +362 -0
  10. package/app/(public)/onboarding.tsx +5 -5
  11. package/app.config.ts +45 -2
  12. package/assets/images/.gitkeep +7 -7
  13. package/components/onboarding/OnboardingScreen.tsx +370 -370
  14. package/components/onboarding/index.ts +2 -2
  15. package/components/providers/SuspenseBoundary.tsx +357 -0
  16. package/components/providers/index.ts +13 -0
  17. package/components/ui/Avatar.tsx +316 -316
  18. package/components/ui/Badge.tsx +416 -416
  19. package/components/ui/BottomSheet.tsx +307 -307
  20. package/components/ui/Checkbox.tsx +261 -261
  21. package/components/ui/OptimizedImage.tsx +369 -369
  22. package/components/ui/Select.tsx +240 -240
  23. package/components/ui/VirtualizedList.tsx +285 -0
  24. package/components/ui/index.ts +23 -18
  25. package/constants/config.ts +97 -54
  26. package/docs/adr/001-state-management.md +79 -79
  27. package/docs/adr/002-styling-approach.md +130 -130
  28. package/docs/adr/003-data-fetching.md +155 -155
  29. package/docs/adr/004-auth-adapter-pattern.md +144 -144
  30. package/docs/adr/README.md +78 -78
  31. package/hooks/index.ts +27 -25
  32. package/hooks/useApi.ts +102 -5
  33. package/hooks/useAuth.tsx +82 -0
  34. package/hooks/useBiometrics.ts +295 -295
  35. package/hooks/useDeepLinking.ts +256 -256
  36. package/hooks/useMFA.ts +499 -0
  37. package/hooks/useNotifications.ts +39 -0
  38. package/hooks/useOffline.ts +32 -2
  39. package/hooks/usePerformance.ts +434 -434
  40. package/hooks/useTheme.tsx +76 -0
  41. package/hooks/useUpdates.ts +358 -358
  42. package/i18n/index.ts +194 -77
  43. package/i18n/locales/ar.json +101 -0
  44. package/i18n/locales/de.json +101 -0
  45. package/i18n/locales/en.json +101 -101
  46. package/i18n/locales/es.json +101 -0
  47. package/i18n/locales/fr.json +101 -101
  48. package/jest.config.js +4 -4
  49. package/maestro/README.md +113 -113
  50. package/maestro/config.yaml +35 -35
  51. package/maestro/flows/login.yaml +62 -62
  52. package/maestro/flows/mfa-login.yaml +92 -0
  53. package/maestro/flows/mfa-setup.yaml +86 -0
  54. package/maestro/flows/navigation.yaml +68 -68
  55. package/maestro/flows/offline-conflict.yaml +101 -0
  56. package/maestro/flows/offline-sync.yaml +128 -0
  57. package/maestro/flows/offline.yaml +60 -60
  58. package/maestro/flows/register.yaml +94 -94
  59. package/package.json +175 -170
  60. package/services/analytics.ts +428 -428
  61. package/services/api.ts +340 -340
  62. package/services/authAdapter.ts +333 -333
  63. package/services/backgroundSync.ts +626 -0
  64. package/services/index.ts +54 -22
  65. package/services/security.ts +229 -0
  66. package/tailwind.config.js +47 -47
  67. package/utils/accessibility.ts +446 -446
  68. package/utils/index.ts +52 -43
  69. package/utils/withAccessibility.tsx +272 -0
@@ -0,0 +1,285 @@
1
+ /**
2
+ * @fileoverview High-performance virtualized list component
3
+ * Wraps @shopify/flash-list for optimal list rendering with large datasets.
4
+ * @module components/ui/VirtualizedList
5
+ */
6
+
7
+ import React, { useCallback, useMemo } from "react";
8
+ import {
9
+ View,
10
+ Text,
11
+ ActivityIndicator,
12
+ RefreshControl,
13
+ StyleSheet,
14
+ } from "react-native";
15
+ import { FlashList, FlashListProps } from "@shopify/flash-list";
16
+ import { useTheme } from "@/hooks/useTheme";
17
+ import { cn } from "@/utils/cn";
18
+
19
+ /**
20
+ * Props for VirtualizedList component
21
+ */
22
+ interface VirtualizedListProps<T> extends Omit<FlashListProps<T>, "renderItem" | "estimatedItemSize"> {
23
+ /**
24
+ * Array of data items to render
25
+ */
26
+ data: T[];
27
+
28
+ /**
29
+ * Render function for each item
30
+ */
31
+ renderItem: (info: { item: T; index: number }) => React.ReactElement | null;
32
+
33
+ /**
34
+ * Unique key extractor for items
35
+ */
36
+ keyExtractor: (item: T, index: number) => string;
37
+
38
+ /**
39
+ * Estimated height of each item for better performance
40
+ * Required for optimal FlashList performance
41
+ */
42
+ estimatedItemSize?: number;
43
+
44
+ /**
45
+ * Whether the list is currently loading
46
+ */
47
+ isLoading?: boolean;
48
+
49
+ /**
50
+ * Whether the list is refreshing (pull-to-refresh)
51
+ */
52
+ isRefreshing?: boolean;
53
+
54
+ /**
55
+ * Callback when user pulls to refresh
56
+ */
57
+ onRefresh?: () => void;
58
+
59
+ /**
60
+ * Whether there's more data to load
61
+ */
62
+ hasMore?: boolean;
63
+
64
+ /**
65
+ * Callback when user scrolls near the end
66
+ */
67
+ onLoadMore?: () => void;
68
+
69
+ /**
70
+ * Component to render when list is empty
71
+ */
72
+ emptyComponent?: React.ReactElement;
73
+
74
+ /**
75
+ * Message to show when list is empty
76
+ */
77
+ emptyMessage?: string;
78
+
79
+ /**
80
+ * Component to render at the bottom when loading more
81
+ */
82
+ loadingMoreComponent?: React.ReactElement;
83
+
84
+ /**
85
+ * Additional class name for container
86
+ */
87
+ className?: string;
88
+
89
+ /**
90
+ * Threshold for onEndReached (0 to 1)
91
+ * @default 0.5
92
+ */
93
+ onEndReachedThreshold?: number;
94
+
95
+ /**
96
+ * Whether to show scroll indicator
97
+ * @default false
98
+ */
99
+ showsVerticalScrollIndicator?: boolean;
100
+
101
+ /**
102
+ * Draw distance for rendering items outside visible area
103
+ * Higher values = smoother scroll but more memory
104
+ * @default 250
105
+ */
106
+ drawDistance?: number;
107
+ }
108
+
109
+ /**
110
+ * Default empty component
111
+ */
112
+ function DefaultEmptyComponent({ message }: { message: string }) {
113
+ const { isDark } = useTheme();
114
+
115
+ return (
116
+ <View className="flex-1 items-center justify-center py-12">
117
+ <Text
118
+ className={cn(
119
+ "text-base",
120
+ isDark ? "text-muted-dark" : "text-muted-light"
121
+ )}
122
+ >
123
+ {message}
124
+ </Text>
125
+ </View>
126
+ );
127
+ }
128
+
129
+ /**
130
+ * Default loading more component
131
+ */
132
+ function DefaultLoadingMoreComponent() {
133
+ return (
134
+ <View className="items-center justify-center py-4">
135
+ <ActivityIndicator size="small" />
136
+ </View>
137
+ );
138
+ }
139
+
140
+ /**
141
+ * High-performance virtualized list component.
142
+ * Optimized for rendering large datasets with minimal memory usage.
143
+ *
144
+ * Features:
145
+ * - Automatic item recycling
146
+ * - Pull-to-refresh support
147
+ * - Infinite scroll (load more)
148
+ * - Empty state handling
149
+ * - Loading states
150
+ *
151
+ * @example
152
+ * ```tsx
153
+ * interface User {
154
+ * id: string;
155
+ * name: string;
156
+ * }
157
+ *
158
+ * function UserList() {
159
+ * const { data, isLoading, refetch, hasNextPage, fetchNextPage } = useUsers();
160
+ *
161
+ * return (
162
+ * <VirtualizedList<User>
163
+ * data={data}
164
+ * renderItem={({ item }) => <UserCard user={item} />}
165
+ * keyExtractor={(item) => item.id}
166
+ * estimatedItemSize={72}
167
+ * isLoading={isLoading}
168
+ * isRefreshing={isLoading}
169
+ * onRefresh={refetch}
170
+ * hasMore={hasNextPage}
171
+ * onLoadMore={fetchNextPage}
172
+ * emptyMessage="No users found"
173
+ * />
174
+ * );
175
+ * }
176
+ * ```
177
+ */
178
+ export function VirtualizedList<T>({
179
+ data,
180
+ renderItem,
181
+ keyExtractor,
182
+ estimatedItemSize = 50,
183
+ isLoading = false,
184
+ isRefreshing = false,
185
+ onRefresh,
186
+ hasMore = false,
187
+ onLoadMore,
188
+ emptyComponent,
189
+ emptyMessage = "No items found",
190
+ loadingMoreComponent,
191
+ className,
192
+ onEndReachedThreshold = 0.5,
193
+ showsVerticalScrollIndicator = false,
194
+ drawDistance = 250,
195
+ ...flashListProps
196
+ }: VirtualizedListProps<T>) {
197
+ const { isDark } = useTheme();
198
+
199
+ // Memoize empty component
200
+ const ListEmptyComponent = useMemo(() => {
201
+ if (isLoading) return null;
202
+ if (emptyComponent) return emptyComponent;
203
+ return <DefaultEmptyComponent message={emptyMessage} />;
204
+ }, [isLoading, emptyComponent, emptyMessage]);
205
+
206
+ // Memoize footer component
207
+ const ListFooterComponent = useCallback(() => {
208
+ if (!hasMore || data.length === 0) return null;
209
+ if (loadingMoreComponent) return loadingMoreComponent;
210
+ return <DefaultLoadingMoreComponent />;
211
+ }, [hasMore, data.length, loadingMoreComponent]);
212
+
213
+ // Handle end reached
214
+ const handleEndReached = useCallback(() => {
215
+ if (hasMore && onLoadMore && !isLoading) {
216
+ onLoadMore();
217
+ }
218
+ }, [hasMore, onLoadMore, isLoading]);
219
+
220
+ // Memoize refresh control
221
+ const refreshControl = useMemo(() => {
222
+ if (!onRefresh) return undefined;
223
+
224
+ return (
225
+ <RefreshControl
226
+ refreshing={isRefreshing}
227
+ onRefresh={onRefresh}
228
+ tintColor={isDark ? "#94a3b8" : "#64748b"}
229
+ colors={["#3b82f6"]}
230
+ />
231
+ );
232
+ }, [onRefresh, isRefreshing, isDark]);
233
+
234
+ // Loading state
235
+ if (isLoading && data.length === 0) {
236
+ return (
237
+ <View className={cn("flex-1 items-center justify-center", className)}>
238
+ <ActivityIndicator size="large" color="#3b82f6" />
239
+ </View>
240
+ );
241
+ }
242
+
243
+ return (
244
+ <FlashList
245
+ data={data}
246
+ renderItem={renderItem}
247
+ keyExtractor={keyExtractor}
248
+ ListEmptyComponent={ListEmptyComponent}
249
+ ListFooterComponent={ListFooterComponent}
250
+ refreshControl={refreshControl}
251
+ onEndReached={handleEndReached}
252
+ onEndReachedThreshold={onEndReachedThreshold}
253
+ showsVerticalScrollIndicator={showsVerticalScrollIndicator}
254
+ // FlashList specific optimizations
255
+ estimatedItemSize={estimatedItemSize}
256
+ drawDistance={drawDistance}
257
+ {...flashListProps}
258
+ contentContainerStyle={[
259
+ styles.container,
260
+ flashListProps.contentContainerStyle,
261
+ ]}
262
+ />
263
+ );
264
+ }
265
+
266
+ const styles = StyleSheet.create({
267
+ container: {
268
+ flexGrow: 1,
269
+ },
270
+ });
271
+
272
+ /**
273
+ * Horizontal virtualized list variant
274
+ */
275
+ export function HorizontalVirtualizedList<T>(
276
+ props: Omit<VirtualizedListProps<T>, "horizontal">
277
+ ) {
278
+ return (
279
+ <VirtualizedList
280
+ {...props}
281
+ horizontal
282
+ showsHorizontalScrollIndicator={false}
283
+ />
284
+ );
285
+ }
@@ -1,18 +1,23 @@
1
- export { Button } from "./Button";
2
- export { Input } from "./Input";
3
- export { Card } from "./Card";
4
- export { Modal } from "./Modal";
5
- export { AnimatedButton } from "./AnimatedButton";
6
- export { AnimatedCard, AnimatedList } from "./AnimatedCard";
7
- export { Select } from "./Select";
8
- export type { SelectOption } from "./Select";
9
- export { Checkbox, CheckboxGroup } from "./Checkbox";
10
- export { BottomSheet, useBottomSheet } from "./BottomSheet";
11
- export type { BottomSheetRef } from "./BottomSheet";
12
- export { Avatar, AvatarGroup } from "./Avatar";
13
- export { Badge, Chip, CountBadge } from "./Badge";
14
- export {
15
- OptimizedImage,
16
- BackgroundImage,
17
- ProgressiveImage,
18
- } from "./OptimizedImage";
1
+ export { Button } from "./Button";
2
+ export { Input } from "./Input";
3
+ export { Card } from "./Card";
4
+ export { Modal } from "./Modal";
5
+ export { AnimatedButton } from "./AnimatedButton";
6
+ export { AnimatedCard, AnimatedList } from "./AnimatedCard";
7
+ export { Select } from "./Select";
8
+ export type { SelectOption } from "./Select";
9
+ export { Checkbox, CheckboxGroup } from "./Checkbox";
10
+ export { BottomSheet, useBottomSheet } from "./BottomSheet";
11
+ export type { BottomSheetRef } from "./BottomSheet";
12
+ export { Avatar, AvatarGroup } from "./Avatar";
13
+ export { Badge, Chip, CountBadge } from "./Badge";
14
+ export {
15
+ OptimizedImage,
16
+ BackgroundImage,
17
+ ProgressiveImage,
18
+ } from "./OptimizedImage";
19
+ export { Skeleton, SkeletonText, SkeletonCircle } from "./Skeleton";
20
+ export {
21
+ VirtualizedList,
22
+ HorizontalVirtualizedList,
23
+ } from "./VirtualizedList";
@@ -1,54 +1,97 @@
1
- import Constants from "expo-constants";
2
-
3
- // Environment detection
4
- export const IS_DEV = __DEV__;
5
- export const IS_PREVIEW =
6
- Constants.expoConfig?.extra?.APP_VARIANT === "preview";
7
- export const IS_PROD = !IS_DEV && !IS_PREVIEW;
8
-
9
- // API Configuration
10
- // TODO: Replace with your actual API URLs
11
- export const API_URL = IS_PROD
12
- ? "https://api.yourapp.com"
13
- : IS_PREVIEW
14
- ? "https://staging-api.yourapp.com"
15
- : "http://localhost:3000";
16
-
17
- // App Configuration
18
- export const APP_NAME = Constants.expoConfig?.name || "YourApp";
19
- export const APP_VERSION = Constants.expoConfig?.version || "1.0.0";
20
- export const APP_SCHEME = Constants.expoConfig?.scheme || "yourapp";
21
-
22
- // Feature Flags
23
- export const FEATURES = {
24
- ENABLE_ANALYTICS: IS_PROD,
25
- ENABLE_CRASH_REPORTING: IS_PROD,
26
- ENABLE_PUSH_NOTIFICATIONS: true,
27
- ENABLE_BIOMETRIC_AUTH: true,
28
- ENABLE_PERFORMANCE_MONITORING: IS_DEV || IS_PREVIEW,
29
- } as const;
30
-
31
- // Export individual flags for convenience
32
- export const ENABLE_ANALYTICS = FEATURES.ENABLE_ANALYTICS;
33
- export const ENABLE_CRASH_REPORTING = FEATURES.ENABLE_CRASH_REPORTING;
34
- export const ENABLE_PUSH_NOTIFICATIONS = FEATURES.ENABLE_PUSH_NOTIFICATIONS;
35
- export const ENABLE_BIOMETRIC_AUTH = FEATURES.ENABLE_BIOMETRIC_AUTH;
36
- export const ENABLE_PERFORMANCE_MONITORING =
37
- FEATURES.ENABLE_PERFORMANCE_MONITORING;
38
-
39
- // Timing Constants
40
- export const TIMING = {
41
- DEBOUNCE_MS: 300,
42
- ANIMATION_DURATION_MS: 200,
43
- TOAST_DURATION_MS: 3000,
44
- API_TIMEOUT_MS: 30000,
45
- } as const;
46
-
47
- // Storage Keys
48
- export const STORAGE_KEYS = {
49
- AUTH_TOKEN: "auth_token",
50
- USER: "auth_user",
51
- THEME: "theme_mode",
52
- ONBOARDING_COMPLETED: "onboarding_completed",
53
- PUSH_TOKEN: "push_token",
54
- } as const;
1
+ import Constants from "expo-constants";
2
+
3
+ // Environment detection
4
+ export const IS_DEV = __DEV__;
5
+ export const IS_PREVIEW =
6
+ Constants.expoConfig?.extra?.APP_VARIANT === "preview";
7
+ export const IS_PROD = !IS_DEV && !IS_PREVIEW;
8
+
9
+ // API Configuration
10
+ // TODO: Replace with your actual API URLs
11
+ export const API_URL = IS_PROD
12
+ ? "https://api.yourapp.com"
13
+ : IS_PREVIEW
14
+ ? "https://staging-api.yourapp.com"
15
+ : "http://localhost:3000";
16
+
17
+ // App Configuration
18
+ export const APP_NAME = Constants.expoConfig?.name || "YourApp";
19
+ export const APP_VERSION = Constants.expoConfig?.version || "1.0.0";
20
+ export const APP_SCHEME = Constants.expoConfig?.scheme || "yourapp";
21
+
22
+ // Feature Flags
23
+ export const FEATURES = {
24
+ ENABLE_ANALYTICS: IS_PROD,
25
+ ENABLE_CRASH_REPORTING: IS_PROD,
26
+ ENABLE_PUSH_NOTIFICATIONS: true,
27
+ ENABLE_BIOMETRIC_AUTH: true,
28
+ ENABLE_PERFORMANCE_MONITORING: IS_DEV || IS_PREVIEW,
29
+ } as const;
30
+
31
+ // Export individual flags for convenience
32
+ export const ENABLE_ANALYTICS = FEATURES.ENABLE_ANALYTICS;
33
+ export const ENABLE_CRASH_REPORTING = FEATURES.ENABLE_CRASH_REPORTING;
34
+ export const ENABLE_PUSH_NOTIFICATIONS = FEATURES.ENABLE_PUSH_NOTIFICATIONS;
35
+ export const ENABLE_BIOMETRIC_AUTH = FEATURES.ENABLE_BIOMETRIC_AUTH;
36
+ export const ENABLE_PERFORMANCE_MONITORING =
37
+ FEATURES.ENABLE_PERFORMANCE_MONITORING;
38
+
39
+ // Timing Constants
40
+ export const TIMING = {
41
+ DEBOUNCE_MS: 300,
42
+ ANIMATION_DURATION_MS: 200,
43
+ TOAST_DURATION_MS: 3000,
44
+ API_TIMEOUT_MS: 30000,
45
+ } as const;
46
+
47
+ // Storage Keys
48
+ export const STORAGE_KEYS = {
49
+ AUTH_TOKEN: "auth_token",
50
+ USER: "auth_user",
51
+ THEME: "theme_mode",
52
+ ONBOARDING_COMPLETED: "onboarding_completed",
53
+ PUSH_TOKEN: "push_token",
54
+ } as const;
55
+
56
+ // Security Configuration
57
+ export const SECURITY = {
58
+ /**
59
+ * SSL Pinning configuration for enhanced network security.
60
+ * Add your API server's certificate public key hashes here.
61
+ *
62
+ * To generate a pin from your certificate:
63
+ * 1. Get your server's certificate: openssl s_client -connect api.yourapp.com:443
64
+ * 2. Extract public key: openssl x509 -pubkey -noout -in cert.pem
65
+ * 3. Generate hash: openssl pkey -pubin -outform der | openssl dgst -sha256 -binary | openssl enc -base64
66
+ *
67
+ * @example
68
+ * SSL_PINS: {
69
+ * "api.yourapp.com": [
70
+ * "sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=", // Primary cert
71
+ * "sha256/BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB=", // Backup cert
72
+ * ],
73
+ * }
74
+ */
75
+ SSL_PINS: {
76
+ // TODO: Add your production API certificate pins
77
+ // "api.yourapp.com": [
78
+ // "sha256/YOUR_CERTIFICATE_HASH_HERE=",
79
+ // ],
80
+ } as Record<string, string[]>,
81
+
82
+ /**
83
+ * Enable SSL pinning in production
84
+ * Set to true once you've configured the SSL_PINS above
85
+ */
86
+ ENABLE_SSL_PINNING: IS_PROD && false, // Enable when pins are configured
87
+
88
+ /**
89
+ * Request signing configuration
90
+ * Used to sign API requests for added security
91
+ */
92
+ REQUEST_SIGNING: {
93
+ ENABLED: false,
94
+ ALGORITHM: "sha256",
95
+ HEADER_NAME: "X-Request-Signature",
96
+ },
97
+ } as const;
@@ -1,79 +1,79 @@
1
- # ADR-001: Use Zustand for State Management
2
-
3
- ## Status
4
-
5
- Accepted
6
-
7
- ## Date
8
-
9
- 2024-01-01
10
-
11
- ## Context
12
-
13
- We need a state management solution for our React Native application. The requirements are:
14
-
15
- 1. Simple API with minimal boilerplate
16
- 2. Good TypeScript support
17
- 3. Small bundle size (mobile performance matters)
18
- 4. Support for persistence
19
- 5. Devtools support for debugging
20
- 6. No need for complex middleware or actions
21
-
22
- Options considered:
23
-
24
- - **Redux Toolkit**: Industry standard, but verbose for our needs
25
- - **MobX**: Powerful but adds complexity with observables
26
- - **Jotai**: Atomic state, good for simple cases
27
- - **Zustand**: Simple, small, flexible
28
- - **React Context**: Built-in, but can cause performance issues
29
-
30
- ## Decision
31
-
32
- We chose **Zustand** for global state management.
33
-
34
- ## Rationale
35
-
36
- 1. **Simplicity**: Creating a store is just a function call
37
-
38
- ```ts
39
- const useStore = create((set) => ({
40
- count: 0,
41
- increment: () => set((s) => ({ count: s.count + 1 })),
42
- }));
43
- ```
44
-
45
- 2. **Bundle size**: ~1KB gzipped vs Redux's ~7KB
46
-
47
- 3. **TypeScript**: Excellent inference, minimal type annotations needed
48
-
49
- 4. **Persistence**: Easy integration with AsyncStorage via middleware
50
-
51
- 5. **No Providers**: Works outside React components, useful for API services
52
-
53
- 6. **Selective subscriptions**: Components only re-render when selected state changes
54
-
55
- ## Consequences
56
-
57
- ### Positive
58
-
59
- - Faster development with less boilerplate
60
- - Smaller bundle size
61
- - Easy to test stores
62
- - Can use stores outside React (e.g., in API handlers)
63
-
64
- ### Negative
65
-
66
- - Less structured than Redux (could lead to inconsistent patterns)
67
- - Smaller ecosystem than Redux
68
- - Team needs to establish conventions
69
-
70
- ### Mitigation
71
-
72
- - Document store patterns in CONTRIBUTING.md
73
- - Use TypeScript for store definitions
74
- - Keep stores focused (one concern per store)
75
-
76
- ## References
77
-
78
- - [Zustand Documentation](https://github.com/pmndrs/zustand)
79
- - [React Native Performance](https://reactnative.dev/docs/performance)
1
+ # ADR-001: Use Zustand for State Management
2
+
3
+ ## Status
4
+
5
+ Accepted
6
+
7
+ ## Date
8
+
9
+ 2024-01-01
10
+
11
+ ## Context
12
+
13
+ We need a state management solution for our React Native application. The requirements are:
14
+
15
+ 1. Simple API with minimal boilerplate
16
+ 2. Good TypeScript support
17
+ 3. Small bundle size (mobile performance matters)
18
+ 4. Support for persistence
19
+ 5. Devtools support for debugging
20
+ 6. No need for complex middleware or actions
21
+
22
+ Options considered:
23
+
24
+ - **Redux Toolkit**: Industry standard, but verbose for our needs
25
+ - **MobX**: Powerful but adds complexity with observables
26
+ - **Jotai**: Atomic state, good for simple cases
27
+ - **Zustand**: Simple, small, flexible
28
+ - **React Context**: Built-in, but can cause performance issues
29
+
30
+ ## Decision
31
+
32
+ We chose **Zustand** for global state management.
33
+
34
+ ## Rationale
35
+
36
+ 1. **Simplicity**: Creating a store is just a function call
37
+
38
+ ```ts
39
+ const useStore = create((set) => ({
40
+ count: 0,
41
+ increment: () => set((s) => ({ count: s.count + 1 })),
42
+ }));
43
+ ```
44
+
45
+ 2. **Bundle size**: ~1KB gzipped vs Redux's ~7KB
46
+
47
+ 3. **TypeScript**: Excellent inference, minimal type annotations needed
48
+
49
+ 4. **Persistence**: Easy integration with AsyncStorage via middleware
50
+
51
+ 5. **No Providers**: Works outside React components, useful for API services
52
+
53
+ 6. **Selective subscriptions**: Components only re-render when selected state changes
54
+
55
+ ## Consequences
56
+
57
+ ### Positive
58
+
59
+ - Faster development with less boilerplate
60
+ - Smaller bundle size
61
+ - Easy to test stores
62
+ - Can use stores outside React (e.g., in API handlers)
63
+
64
+ ### Negative
65
+
66
+ - Less structured than Redux (could lead to inconsistent patterns)
67
+ - Smaller ecosystem than Redux
68
+ - Team needs to establish conventions
69
+
70
+ ### Mitigation
71
+
72
+ - Document store patterns in CONTRIBUTING.md
73
+ - Use TypeScript for store definitions
74
+ - Keep stores focused (one concern per store)
75
+
76
+ ## References
77
+
78
+ - [Zustand Documentation](https://github.com/pmndrs/zustand)
79
+ - [React Native Performance](https://reactnative.dev/docs/performance)