WellNuo/stores/README.md

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