@mr.dj2u/knowledge 0.1.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/dist/content/checklists/ship-test-loop.md +16 -0
- package/dist/content/checklists/unified-agent-bundle-validation.md +8 -0
- package/dist/content/examples/ship-test-loop.md +13 -0
- package/dist/content/examples/unified-agent-bundle-bootstrap.md +8 -0
- package/dist/content/guides/animation-performance.md +30 -0
- package/dist/content/guides/post-create-onboarding.md +113 -0
- package/dist/content/patterns/api/api-routes.md +314 -0
- package/dist/content/patterns/api/error-handling.md +311 -0
- package/dist/content/patterns/database/drizzle-schema.md +280 -0
- package/dist/content/patterns/database/migrations.md +365 -0
- package/dist/content/patterns/database/query-organization.md +537 -0
- package/dist/content/patterns/database/relations.md +450 -0
- package/dist/content/patterns/deployment/build-configuration.md +452 -0
- package/dist/content/patterns/deployment/ci-cd-patterns.md +448 -0
- package/dist/content/patterns/deployment/environment-config.md +380 -0
- package/dist/content/patterns/deployment/hosting-setup.md +425 -0
- package/dist/content/patterns/project/configuration-patterns.md +459 -0
- package/dist/content/patterns/project/documentation-org.md +506 -0
- package/dist/content/patterns/project/folder-structure.md +398 -0
- package/dist/content/patterns/project/library-exports.md +465 -0
- package/dist/content/patterns/project/monorepo-structure.md +500 -0
- package/dist/content/patterns/routing/dynamic-routes.md +221 -0
- package/dist/content/patterns/routing/file-based-routing.md +186 -0
- package/dist/content/patterns/routing/route-groups.md +429 -0
- package/dist/content/patterns/state/persistence-middleware.md +521 -0
- package/dist/content/patterns/state/selector-hooks.md +538 -0
- package/dist/content/patterns/state/store-organization.md +539 -0
- package/dist/content/patterns/state/zustand-patterns.md +348 -0
- package/dist/content/patterns/styling/component-styling.md +468 -0
- package/dist/content/patterns/styling/responsive-patterns.md +398 -0
- package/dist/content/patterns/styling/theme-configuration.md +426 -0
- package/dist/content/patterns/styling/uniwind-setup.md +412 -0
- package/dist/content/prompts/continue-development.md +27 -0
- package/dist/content/prompts/create-expo-super-stack.md +29 -0
- package/dist/content/prompts/fix-seo.md +29 -0
- package/dist/content/prompts/onboard-new-expo-app.md +11 -0
- package/dist/content/prompts/prepare-deploy.md +29 -0
- package/dist/content/prompts/project-research-plan.md +29 -0
- package/dist/content/prompts/review-expo-project.md +29 -0
- package/dist/content/prompts/run-doctor.md +30 -0
- package/dist/content/prompts/ship-test-loop.md +24 -0
- package/dist/content/reference/create-expo-stack-uniwind.md +29 -0
- package/dist/content/reference/doctor-dogfood.md +42 -0
- package/dist/content/reference/mcp-sdk-transport.md +30 -0
- package/dist/content/reference/package-ci-patterns.md +24 -0
- package/dist/content/reference/reference-repo-evacuation.md +31 -0
- package/dist/content/resource-index.json +67 -0
- package/dist/content/rules/app-folder-architecture.md +13 -0
- package/dist/content/rules/env-hygiene.md +9 -0
- package/dist/content/rules/seo-metadata.md +7 -0
- package/dist/content/rules/ssr-safety.md +9 -0
- package/dist/content/skills/api-routes.md +33 -0
- package/dist/content/skills/continue-development.md +31 -0
- package/dist/content/skills/debugging.md +31 -0
- package/dist/content/skills/deployment.md +32 -0
- package/dist/content/skills/dev-server-management.md +31 -0
- package/dist/content/skills/env-vars.md +32 -0
- package/dist/content/skills/expo-router-architecture.md +33 -0
- package/dist/content/skills/expo-ssr-safety.md +32 -0
- package/dist/content/skills/plugin-creation.md +41 -0
- package/dist/content/skills/production-server-patterns.md +31 -0
- package/dist/content/skills/project-onboarding.md +31 -0
- package/dist/content/skills/research-plan-intake.md +31 -0
- package/dist/content/skills/seo-metadata.md +31 -0
- package/dist/content/skills/super-stack-startup.md +31 -0
- package/dist/content/skills/uniwind-theming.md +32 -0
- package/dist/index.d.ts +46 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +369 -0
- package/dist/index.js.map +1 -0
- package/dist/patterns/index.d.ts +78 -0
- package/dist/patterns/index.d.ts.map +1 -0
- package/dist/patterns/index.js +264 -0
- package/dist/patterns/index.js.map +1 -0
- package/dist/prompts/index.d.ts +35 -0
- package/dist/prompts/index.d.ts.map +1 -0
- package/dist/prompts/index.js +270 -0
- package/dist/prompts/index.js.map +1 -0
- package/dist/skills/index.d.ts +3 -0
- package/dist/skills/index.d.ts.map +1 -0
- package/dist/skills/index.js +2 -0
- package/dist/skills/index.js.map +1 -0
- package/package.json +50 -0
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
# Zustand State Management
|
|
2
|
+
|
|
3
|
+
## Description
|
|
4
|
+
|
|
5
|
+
Zustand provides lightweight atomic state management with TypeScript type inference, optional persistence middleware, and selector hooks for optimal re-renders. Stores are created without providers, maintaining flat structure with computed properties derived from state, and AsyncStorage/localStorage integration for cross-platform persistence.
|
|
6
|
+
|
|
7
|
+
## When to Use
|
|
8
|
+
|
|
9
|
+
**Use Zustand** for:
|
|
10
|
+
- ✅ Global application state (auth, user, settings)
|
|
11
|
+
- ✅ Feature-level state with persistence needs
|
|
12
|
+
- ✅ State requiring type-safe selectors
|
|
13
|
+
- ✅ Atomic updates without action dispatchers
|
|
14
|
+
|
|
15
|
+
## Code Example
|
|
16
|
+
|
|
17
|
+
### Basic Store with Persistence
|
|
18
|
+
|
|
19
|
+
```typescript
|
|
20
|
+
// File: src/store/authStore.ts
|
|
21
|
+
import { create } from 'zustand';
|
|
22
|
+
import { persist, createJSONStorage } from 'zustand/middleware';
|
|
23
|
+
import AsyncStorage from '@react-native-async-storage/async-storage';
|
|
24
|
+
|
|
25
|
+
export interface AuthState {
|
|
26
|
+
// State
|
|
27
|
+
user: User | null;
|
|
28
|
+
profile: Profile | null;
|
|
29
|
+
session: Session | null;
|
|
30
|
+
isLoggedIn: boolean;
|
|
31
|
+
loading: boolean;
|
|
32
|
+
|
|
33
|
+
// Computed properties (derived state)
|
|
34
|
+
get isAdult(): boolean;
|
|
35
|
+
get canUseSocialFeatures(): boolean;
|
|
36
|
+
get isVip(): boolean;
|
|
37
|
+
|
|
38
|
+
// Actions
|
|
39
|
+
setUser: (user: User | null) => void;
|
|
40
|
+
setProfile: (profile: Profile | null) => void;
|
|
41
|
+
signOut: () => Promise<void>;
|
|
42
|
+
initializeAuth: () => Promise<void>;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export const useAuthStore = create<AuthState>()(
|
|
46
|
+
persist(
|
|
47
|
+
(set, get) => ({
|
|
48
|
+
// Initial state
|
|
49
|
+
user: null,
|
|
50
|
+
profile: null,
|
|
51
|
+
session: null,
|
|
52
|
+
isLoggedIn: false,
|
|
53
|
+
loading: true,
|
|
54
|
+
|
|
55
|
+
// Computed properties
|
|
56
|
+
get isAdult(): boolean {
|
|
57
|
+
const profile = get().profile;
|
|
58
|
+
if (!profile?.dateOfBirth) return false;
|
|
59
|
+
const age = calculateAge(profile.dateOfBirth);
|
|
60
|
+
return age >= 18;
|
|
61
|
+
},
|
|
62
|
+
|
|
63
|
+
get canUseSocialFeatures(): boolean {
|
|
64
|
+
return get().isLoggedIn && get().isAdult;
|
|
65
|
+
},
|
|
66
|
+
|
|
67
|
+
get isVip(): boolean {
|
|
68
|
+
return get().profile?.vipStatus === true;
|
|
69
|
+
},
|
|
70
|
+
|
|
71
|
+
// Actions
|
|
72
|
+
setUser: (user) => set({ user, isLoggedIn: !!user }),
|
|
73
|
+
|
|
74
|
+
setProfile: (profile) => set({ profile }),
|
|
75
|
+
|
|
76
|
+
signOut: async () => {
|
|
77
|
+
await supabase.auth.signOut();
|
|
78
|
+
set({
|
|
79
|
+
user: null,
|
|
80
|
+
profile: null,
|
|
81
|
+
session: null,
|
|
82
|
+
isLoggedIn: false,
|
|
83
|
+
});
|
|
84
|
+
},
|
|
85
|
+
|
|
86
|
+
initializeAuth: async () => {
|
|
87
|
+
set({ loading: true });
|
|
88
|
+
try {
|
|
89
|
+
const { data } = await supabase.auth.getSession();
|
|
90
|
+
if (data.session?.user) {
|
|
91
|
+
const profile = await fetchProfile(data.session.user.id);
|
|
92
|
+
set({
|
|
93
|
+
user: data.session.user,
|
|
94
|
+
profile,
|
|
95
|
+
session: data.session,
|
|
96
|
+
isLoggedIn: true,
|
|
97
|
+
loading: false,
|
|
98
|
+
});
|
|
99
|
+
} else {
|
|
100
|
+
set({ loading: false });
|
|
101
|
+
}
|
|
102
|
+
} catch (error) {
|
|
103
|
+
console.error('Auth init error:', error);
|
|
104
|
+
set({ loading: false });
|
|
105
|
+
}
|
|
106
|
+
},
|
|
107
|
+
}),
|
|
108
|
+
{
|
|
109
|
+
name: 'auth-storage',
|
|
110
|
+
storage: createJSONStorage(() => AsyncStorage),
|
|
111
|
+
// Only persist specific fields
|
|
112
|
+
partialize: (state) => ({
|
|
113
|
+
user: state.user,
|
|
114
|
+
profile: state.profile,
|
|
115
|
+
session: state.session,
|
|
116
|
+
}),
|
|
117
|
+
}
|
|
118
|
+
)
|
|
119
|
+
);
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
**From:** PokePages/src/store/authStore.ts, core-monorepo pattern
|
|
123
|
+
|
|
124
|
+
### Selector Hooks for Performance
|
|
125
|
+
|
|
126
|
+
```typescript
|
|
127
|
+
// ✅ GOOD - Custom selector hooks prevent unnecessary re-renders
|
|
128
|
+
|
|
129
|
+
// Only re-renders when user email changes
|
|
130
|
+
export const useUserEmail = () =>
|
|
131
|
+
useAuthStore((state) => state.user?.email);
|
|
132
|
+
|
|
133
|
+
// Only re-renders when isAdult computed property changes
|
|
134
|
+
export const useIsAdult = () =>
|
|
135
|
+
useAuthStore((state) => state.isAdult);
|
|
136
|
+
|
|
137
|
+
// Multi-selector in single hook
|
|
138
|
+
export const useUserWithProfile = () =>
|
|
139
|
+
useAuthStore((state) => ({
|
|
140
|
+
user: state.user,
|
|
141
|
+
profile: state.profile,
|
|
142
|
+
isLoggedIn: state.isLoggedIn,
|
|
143
|
+
}));
|
|
144
|
+
|
|
145
|
+
// Usage in component
|
|
146
|
+
function UserCard() {
|
|
147
|
+
const email = useUserEmail();
|
|
148
|
+
const { profile } = useUserWithProfile();
|
|
149
|
+
|
|
150
|
+
// Only re-renders when email or profile object reference changes
|
|
151
|
+
return <Text>{email}</Text>;
|
|
152
|
+
}
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
### Store for Favorites with Toggle
|
|
156
|
+
|
|
157
|
+
```typescript
|
|
158
|
+
// File: src/store/favoriteFeaturesStore.ts
|
|
159
|
+
import { create } from 'zustand';
|
|
160
|
+
import { persist, createJSONStorage } from 'zustand/middleware';
|
|
161
|
+
import AsyncStorage from '@react-native-async-storage/async-storage';
|
|
162
|
+
|
|
163
|
+
export interface FavoritesState {
|
|
164
|
+
favorites: Record<string, boolean>;
|
|
165
|
+
toggleFavorite: (key: string) => void;
|
|
166
|
+
isFavorite: (key: string) => boolean;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export const useFavoritesStore = create<FavoritesState>()(
|
|
170
|
+
persist(
|
|
171
|
+
(set, get) => ({
|
|
172
|
+
favorites: {},
|
|
173
|
+
|
|
174
|
+
toggleFavorite: (key) =>
|
|
175
|
+
set((state) => ({
|
|
176
|
+
favorites: {
|
|
177
|
+
...state.favorites,
|
|
178
|
+
[key]: !state.favorites[key],
|
|
179
|
+
},
|
|
180
|
+
})),
|
|
181
|
+
|
|
182
|
+
isFavorite: (key) => !!get().favorites[key],
|
|
183
|
+
}),
|
|
184
|
+
{
|
|
185
|
+
name: 'favorites-storage',
|
|
186
|
+
storage: createJSONStorage(() => AsyncStorage),
|
|
187
|
+
}
|
|
188
|
+
)
|
|
189
|
+
);
|
|
190
|
+
|
|
191
|
+
// Selector hooks
|
|
192
|
+
export const useIsFavorite = (key: string) =>
|
|
193
|
+
useFavoritesStore((state) => state.isFavorite(key));
|
|
194
|
+
|
|
195
|
+
export const useToggleFavorite = () =>
|
|
196
|
+
useFavoritesStore((state) => state.toggleFavorite);
|
|
197
|
+
|
|
198
|
+
// Component usage
|
|
199
|
+
function FavoriteButton({ pageKey }: { pageKey: string }) {
|
|
200
|
+
const isFavorite = useIsFavorite(pageKey);
|
|
201
|
+
const toggle = useToggleFavorite();
|
|
202
|
+
|
|
203
|
+
return (
|
|
204
|
+
<Pressable onPress={() => toggle(pageKey)}>
|
|
205
|
+
<Text>{isFavorite ? '⭐' : '☆'}</Text>
|
|
206
|
+
</Pressable>
|
|
207
|
+
);
|
|
208
|
+
}
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
**From:** PokePages/src/store/favoriteFeaturesStore.ts
|
|
212
|
+
|
|
213
|
+
## Configuration
|
|
214
|
+
|
|
215
|
+
### Cross-Platform Storage Setup
|
|
216
|
+
|
|
217
|
+
```typescript
|
|
218
|
+
// File: src/utils/createCrossPlatformStorage.ts
|
|
219
|
+
import { Platform } from 'react-native';
|
|
220
|
+
import AsyncStorage from '@react-native-async-storage/async-storage';
|
|
221
|
+
import { createJSONStorage } from 'zustand/middleware';
|
|
222
|
+
|
|
223
|
+
export function createCrossPlatformStorage() {
|
|
224
|
+
if (Platform.OS === 'web') {
|
|
225
|
+
return createJSONStorage(() => ({
|
|
226
|
+
getItem: (key) => localStorage.getItem(key),
|
|
227
|
+
setItem: (key, value) => localStorage.setItem(key, value),
|
|
228
|
+
removeItem: (key) => localStorage.removeItem(key),
|
|
229
|
+
}));
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
return createJSONStorage(() => AsyncStorage);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Use in stores
|
|
236
|
+
export const useStore = create<State>()(
|
|
237
|
+
persist(
|
|
238
|
+
(set) => ({ /* state */ }),
|
|
239
|
+
{
|
|
240
|
+
storage: createCrossPlatformStorage(),
|
|
241
|
+
}
|
|
242
|
+
)
|
|
243
|
+
);
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
## Best Practices
|
|
247
|
+
|
|
248
|
+
### ✅ DO
|
|
249
|
+
|
|
250
|
+
1. **Use selector hooks** to prevent unnecessary re-renders
|
|
251
|
+
```typescript
|
|
252
|
+
// ✅ GOOD - only subscribes to email
|
|
253
|
+
const email = useAuthStore((state) => state.user?.email);
|
|
254
|
+
|
|
255
|
+
// ❌ BAD - subscribes to entire store
|
|
256
|
+
const { user, profile, session, ... } = useAuthStore();
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
2. **Keep stores flat** — avoid nested objects
|
|
260
|
+
```typescript
|
|
261
|
+
// ✅ GOOD - flat structure
|
|
262
|
+
userId: '123'
|
|
263
|
+
userName: 'alice'
|
|
264
|
+
userEmail: 'alice@example.com'
|
|
265
|
+
|
|
266
|
+
// ❌ BAD - nested (causes unnecessary re-renders)
|
|
267
|
+
user: { id: '123', name: 'alice', email: '...' }
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
3. **Use computed properties (getters)** for derived state
|
|
271
|
+
```typescript
|
|
272
|
+
get isAdult(): boolean {
|
|
273
|
+
return calculateAge(get().profile?.dateOfBirth) >= 18;
|
|
274
|
+
}
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
4. **Partial persistence** — only persist necessary fields
|
|
278
|
+
```typescript
|
|
279
|
+
partialize: (state) => ({
|
|
280
|
+
user: state.user,
|
|
281
|
+
theme: state.theme,
|
|
282
|
+
// Don't persist: loading, errors, temporary UI state
|
|
283
|
+
})
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
5. **Split stores** by domain/feature
|
|
287
|
+
```typescript
|
|
288
|
+
useAuthStore() // auth + user
|
|
289
|
+
useThemeStore() // app theme
|
|
290
|
+
useFavoritesStore() // favorites
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
### ❌ DON'T
|
|
294
|
+
|
|
295
|
+
1. **Don't use context** for everything
|
|
296
|
+
```typescript
|
|
297
|
+
// ❌ AVOID - causes re-renders of all consumers
|
|
298
|
+
<UserContext.Provider value={{ user, setUser, ... }}>
|
|
299
|
+
|
|
300
|
+
// ✅ USE - only subscribed components re-render
|
|
301
|
+
const user = useUserStore((state) => state.user);
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
2. **Don't persist state that changes frequently**
|
|
305
|
+
```typescript
|
|
306
|
+
// ❌ BAD - persists on every keystroke
|
|
307
|
+
persist((set) => ({
|
|
308
|
+
searchQuery: '',
|
|
309
|
+
setSearchQuery: (q) => set({ searchQuery: q })
|
|
310
|
+
}))
|
|
311
|
+
|
|
312
|
+
// ✅ GOOD - only persist final state
|
|
313
|
+
onChangeText={(q) => dispatch(setSearchQuery(q))} // local only
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
3. **Don't store derived data** that can be computed
|
|
317
|
+
```typescript
|
|
318
|
+
// ❌ BAD - will get out of sync
|
|
319
|
+
user: { id: '1', name: 'alice', email: 'alice@ex.com' }
|
|
320
|
+
userDisplayName: 'alice' // Duplicate!
|
|
321
|
+
|
|
322
|
+
// ✅ GOOD - compute on demand
|
|
323
|
+
get userDisplayName() {
|
|
324
|
+
return get().user?.name;
|
|
325
|
+
}
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
4. **Don't forget to handle undefined** from selectors
|
|
329
|
+
```typescript
|
|
330
|
+
// ❌ RISKY
|
|
331
|
+
const email = useAuthStore((state) => state.user?.email);
|
|
332
|
+
return <Text>{email}</Text>; // Could be undefined
|
|
333
|
+
|
|
334
|
+
// ✅ SAFE
|
|
335
|
+
const email = useAuthStore((state) => state.user?.email);
|
|
336
|
+
return <Text>{email || 'No email'}</Text>;
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
## Related Patterns
|
|
340
|
+
|
|
341
|
+
- [Store Organization](./store-organization.md) — Multi-store patterns
|
|
342
|
+
- [Persistence Middleware](./persistence-middleware.md) — AsyncStorage integration
|
|
343
|
+
- [Selector Hooks](./selector-hooks.md) — Performance optimization
|
|
344
|
+
|
|
345
|
+
---
|
|
346
|
+
|
|
347
|
+
*Pattern extracted from production repositories: PokePages, core-monorepo, time2pay*
|
|
348
|
+
*Files: PokePages/src\store\authStore.ts, favoriteFeaturesStore.ts*
|