179 lines
4.0 KiB
Markdown
179 lines
4.0 KiB
Markdown
# WellNuo Stores
|
|
|
|
State management using Zustand for performant, scalable React state.
|
|
|
|
## Why Zustand?
|
|
|
|
Zustand provides several advantages over Context API:
|
|
|
|
1. **Better Performance** - Only components using specific values re-render
|
|
2. **Simpler API** - No provider wrappers needed
|
|
3. **TypeScript First** - Excellent type inference
|
|
4. **Flexible** - Can be used outside React components
|
|
5. **Smaller Bundle** - Lightweight library (~1KB)
|
|
|
|
## Available Stores
|
|
|
|
### Auth Store (`authStore.ts`)
|
|
|
|
Manages authentication state and user sessions.
|
|
|
|
**Features:**
|
|
- OTP authentication flow (email → OTP → verify)
|
|
- Automatic session checking on app start
|
|
- Auto-logout on 401 unauthorized
|
|
- User profile management
|
|
- Optimized selector hooks
|
|
|
|
**Basic Usage:**
|
|
|
|
```tsx
|
|
import { useAuthStore, initAuthStore } from '@/stores/authStore';
|
|
|
|
// 1. Initialize once at app start (in app/_layout.tsx)
|
|
export default function RootLayout() {
|
|
useEffect(() => {
|
|
initAuthStore();
|
|
}, []);
|
|
}
|
|
|
|
// 2. Use in components
|
|
function MyComponent() {
|
|
const { user, isAuthenticated, logout } = useAuthStore();
|
|
|
|
return (
|
|
<View>
|
|
{isAuthenticated ? (
|
|
<Text>Welcome {user?.firstName}</Text>
|
|
) : (
|
|
<Text>Please login</Text>
|
|
)}
|
|
</View>
|
|
);
|
|
}
|
|
```
|
|
|
|
**Optimized Selectors:**
|
|
|
|
Use selector hooks when you only need specific values - prevents unnecessary re-renders:
|
|
|
|
```tsx
|
|
import { useAuthUser, useIsAuthenticated } from '@/stores/authStore';
|
|
|
|
function UserProfile() {
|
|
// Only re-renders when user changes
|
|
const user = useAuthUser();
|
|
|
|
return <Text>{user?.email}</Text>;
|
|
}
|
|
|
|
function LoginButton() {
|
|
// Only re-renders when isAuthenticated changes
|
|
const isAuthenticated = useIsAuthenticated();
|
|
|
|
return isAuthenticated ? <LogoutButton /> : <LoginButton />;
|
|
}
|
|
```
|
|
|
|
**Authentication Flow:**
|
|
|
|
```tsx
|
|
function LoginScreen() {
|
|
const { checkEmail, requestOtp, verifyOtp, error } = useAuthStore();
|
|
|
|
// Step 1: Check if email exists
|
|
const emailResult = await checkEmail('user@example.com');
|
|
|
|
// Step 2: Request OTP code
|
|
const otpResult = await requestOtp('user@example.com');
|
|
|
|
// Step 3: Verify OTP and authenticate
|
|
const success = await verifyOtp('user@example.com', '123456');
|
|
|
|
if (success) {
|
|
// User is now authenticated!
|
|
// Navigate to dashboard
|
|
}
|
|
}
|
|
```
|
|
|
|
**State Updates:**
|
|
|
|
```tsx
|
|
function ProfileScreen() {
|
|
const { updateUser } = useAuthStore();
|
|
|
|
// Update local state (doesn't call API)
|
|
updateUser({ firstName: 'John', lastName: 'Doe' });
|
|
|
|
// To persist to server, also call:
|
|
await api.updateProfile({ firstName: 'John', lastName: 'Doe' });
|
|
}
|
|
```
|
|
|
|
**Outside React:**
|
|
|
|
```tsx
|
|
import { useAuthStore } from '@/stores/authStore';
|
|
|
|
// In utility functions or non-component code
|
|
function isUserAuthenticated() {
|
|
return useAuthStore.getState().isAuthenticated;
|
|
}
|
|
|
|
// Trigger logout from anywhere
|
|
function forceLogout() {
|
|
useAuthStore.getState().logout();
|
|
}
|
|
```
|
|
|
|
## Testing
|
|
|
|
All stores have comprehensive test coverage:
|
|
|
|
```bash
|
|
# Run all store tests
|
|
npm test -- stores/
|
|
|
|
# Run specific store tests
|
|
npm test -- authStore.test.ts
|
|
|
|
# With coverage
|
|
npm test -- --coverage stores/
|
|
```
|
|
|
|
## Best Practices
|
|
|
|
1. **Use Selectors** - Prefer `useAuthUser()` over `useAuthStore((state) => state.user)`
|
|
2. **Initialize Once** - Call `initAuthStore()` only once at app startup
|
|
3. **Granular Subscriptions** - Only subscribe to values you need
|
|
4. **Type Safety** - Store is fully typed, use TypeScript benefits
|
|
5. **Server Sync** - Remember to call API after updating store state
|
|
|
|
## Migration from Context
|
|
|
|
If migrating from AuthContext:
|
|
|
|
**Before (Context):**
|
|
```tsx
|
|
const { user, isAuthenticated } = useAuth();
|
|
```
|
|
|
|
**After (Zustand):**
|
|
```tsx
|
|
const { user, isAuthenticated } = useAuthStore();
|
|
```
|
|
|
|
The API is almost identical! Main difference: no Provider wrapper needed.
|
|
|
|
## Adding New Stores
|
|
|
|
Follow the pattern in `authStore.ts`:
|
|
|
|
1. Define state interface
|
|
2. Define actions interface
|
|
3. Create store with `create<State & Actions>()`
|
|
4. Export store and selector hooks
|
|
5. Add comprehensive tests
|
|
6. Document in this README
|