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:

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

  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):

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:

  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