4.0 KiB
4.0 KiB
WellNuo Stores
State management using Zustand for performant, scalable React state.
Why Zustand?
Zustand provides several advantages over Context API:
- Better Performance - Only components using specific values re-render
- Simpler API - No provider wrappers needed
- TypeScript First - Excellent type inference
- Flexible - Can be used outside React components
- 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:
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:
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:
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:
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:
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:
# Run all store tests
npm test -- stores/
# Run specific store tests
npm test -- authStore.test.ts
# With coverage
npm test -- --coverage stores/
Best Practices
- Use Selectors - Prefer
useAuthUser()overuseAuthStore((state) => state.user) - Initialize Once - Call
initAuthStore()only once at app startup - Granular Subscriptions - Only subscribe to values you need
- Type Safety - Store is fully typed, use TypeScript benefits
- Server Sync - Remember to call API after updating store state
Migration from Context
If migrating from AuthContext:
Before (Context):
const { user, isAuthenticated } = useAuth();
After (Zustand):
const { user, isAuthenticated } = useAuthStore();
The API is almost identical! Main difference: no Provider wrapper needed.
Adding New Stores
Follow the pattern in authStore.ts:
- Define state interface
- Define actions interface
- Create store with
create<State & Actions>() - Export store and selector hooks
- Add comprehensive tests
- Document in this README