Skip to content

Implement Jotai atoms for global state management #2093

@koistya

Description

@koistya

📋 Overview

We need to implement a structured global state management system using Jotai atoms. This will help centralize and organize our application state in a type-safe, reactive way. The goal is to create domain-specific atom files that will manage different aspects of our application state.

🎯 Objective

Create a well-organized atoms directory structure with domain-specific atom files for managing global application state using Jotai's atomic state management pattern.

📁 Directory Structure to Create

apps/web/atoms/
├── user.ts        # Authentication and user state
├── theme.ts       # UI preferences and theming
├── settings.ts    # Application configuration
└── index.ts       # Clean re-exports

✅ Tasks

1. Create apps/web/atoms/user.ts

Implement atoms for user authentication and profile state:

import { atom } from 'jotai';
import { atomWithStorage } from 'jotai/utils';

// Primitive atoms (base state)
export const userAtom = atom<User | null>(null);
export const isAuthenticatedAtom = atom<boolean>(false);
export const authTokenAtom = atomWithStorage<string | null>('authToken', null);

// Derived atoms (computed values)
export const userDisplayNameAtom = atom(
  (get) => get(userAtom)?.displayName || get(userAtom)?.email || 'Guest'
);

export const userInitialsAtom = atom((get) => {
  const user = get(userAtom);
  if (\!user) return 'G';
  
  const name = user.displayName || user.email;
  return name
    .split(' ')
    .map(n => n[0])
    .join('')
    .toUpperCase()
    .slice(0, 2);
});

2. Create apps/web/atoms/theme.ts

Implement atoms for UI preferences and theming:

import { atom } from 'jotai';
import { atomWithStorage } from 'jotai/utils';

type Theme = 'light' | 'dark' | 'system';
type ColorScheme = 'blue' | 'green' | 'purple' | 'orange';

// Primitive atoms with localStorage persistence
export const themeAtom = atomWithStorage<Theme>('theme', 'system');
export const colorSchemeAtom = atomWithStorage<ColorScheme>('colorScheme', 'blue');
export const sidebarCollapsedAtom = atomWithStorage<boolean>('sidebarCollapsed', false);

// Derived atoms
export const resolvedThemeAtom = atom((get) => {
  const theme = get(themeAtom);
  if (theme \!== 'system') return theme;
  
  // Check system preference
  return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
});

export const themeClassAtom = atom((get) => {
  const theme = get(resolvedThemeAtom);
  const color = get(colorSchemeAtom);
  return `theme-${theme} color-${color}`;
});

3. Create apps/web/atoms/settings.ts

Implement atoms for application configuration:

import { atom } from 'jotai';
import { atomWithStorage } from 'jotai/utils';

interface AppSettings {
  notifications: {
    email: boolean;
    push: boolean;
    sound: boolean;
  };
  privacy: {
    shareAnalytics: boolean;
    showOnlineStatus: boolean;
  };
  locale: string;
  timezone: string;
}

// Default settings
const defaultSettings: AppSettings = {
  notifications: {
    email: true,
    push: true,
    sound: false,
  },
  privacy: {
    shareAnalytics: false,
    showOnlineStatus: true,
  },
  locale: 'en-US',
  timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
};

// Primitive atom with localStorage persistence
export const settingsAtom = atomWithStorage<AppSettings>('appSettings', defaultSettings);

// Derived atoms for specific settings
export const notificationsEnabledAtom = atom(
  (get) => {
    const settings = get(settingsAtom);
    return settings.notifications.email || settings.notifications.push;
  }
);

export const localeAtom = atom(
  (get) => get(settingsAtom).locale,
  (get, set, locale: string) => {
    const settings = get(settingsAtom);
    set(settingsAtom, { ...settings, locale });
  }
);

4. Create apps/web/atoms/index.ts

Create clean re-exports for all atoms:

// User atoms
export {
  userAtom,
  isAuthenticatedAtom,
  authTokenAtom,
  userDisplayNameAtom,
  userInitialsAtom,
} from './user';

// Theme atoms
export {
  themeAtom,
  colorSchemeAtom,
  sidebarCollapsedAtom,
  resolvedThemeAtom,
  themeClassAtom,
} from './theme';

// Settings atoms
export {
  settingsAtom,
  notificationsEnabledAtom,
  localeAtom,
} from './settings';

💡 Implementation Tips

  1. Use TypeScript: Define proper types for all atom values
  2. Leverage atomWithStorage: Use this for values that should persist across sessions
  3. Create derived atoms: Use read-only atoms for computed values
  4. Keep atoms focused: Each atom should represent a single piece of state
  5. Use atom families: For dynamic collections of similar atoms (if needed)

📚 Example Usage in Components

import { useAtom, useAtomValue } from 'jotai';
import { userAtom, themeAtom, userDisplayNameAtom } from '@/atoms';

function UserProfile() {
  const [user, setUser] = useAtom(userAtom);
  const displayName = useAtomValue(userDisplayNameAtom);
  const [theme, setTheme] = useAtom(themeAtom);
  
  return (
    <div>
      <h1>Welcome, {displayName}\!</h1>
      <button onClick={() => setTheme(theme === 'dark' ? 'light' : 'dark')}>
        Toggle Theme
      </button>
    </div>
  );
}

🧪 Testing Requirements

  • Write unit tests for derived atoms logic
  • Test localStorage persistence for atomWithStorage
  • Verify atom updates trigger component re-renders
  • Test edge cases (null users, invalid settings, etc.)

📖 Resources

⚡ Priority

High - This is foundational work that will improve state management across the entire application.

🎓 Good First Issue Notes

This is an excellent first issue because:

  • Clear, well-defined scope
  • Good introduction to our codebase structure
  • Learn modern React state management patterns
  • No complex business logic required
  • Can be implemented incrementally

❓ Questions?

Feel free to ask questions in the comments! We're here to help you succeed with this contribution.

🔍 Definition of Done

  • All atom files created with proper TypeScript types
  • Atoms follow Jotai best practices
  • Clean re-exports in index.ts
  • Code follows project conventions (functional, modern TypeScript)
  • Basic unit tests for derived atoms
  • No ESLint warnings or TypeScript errors

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    Status

    Ready

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions