Skip to content

useStoreManager API

The useStoreManager hook provides low-level access to the internal StoreManager instance for advanced store management scenarios in the Declarative Store Pattern.

Prerequisites

For basic store setup and context creation, see Basic Store Setup.

This document demonstrates API usage using the store setup:

Basic Usage

Getting Store Manager

tsx
import { createStoreContext, useStoreValue } from '@context-action/react';

// Using the established store patterns from setup guide
const {
  Provider: UserStoreProvider,
  useStore: useUserStore,
  useStoreManager: useUserStoreManager
} = createStoreContext('User', {
  profile: {
    initialValue: { id: '', name: '', email: '', role: 'guest' as const },
    strategy: 'shallow' as const
  },
  preferences: {
    initialValue: { theme: 'light' as const, language: 'en', notifications: true },
    strategy: 'shallow' as const
  },
  session: {
    initialValue: { isAuthenticated: false, permissions: [], lastActivity: 0 },
    strategy: 'shallow' as const
  }
});

function MyComponent() {
  const manager = useUserStoreManager();
  
  // Get store instances directly using setup pattern
  const profileStore = manager.getStore('profile');
  const preferencesStore = manager.getStore('preferences');
  
  // Component logic here
}

Store Operations

tsx
function UserManager() {
  const manager = useUserStoreManager();
  const profileStore = useUserStore('profile');
  const profile = useStoreValue(profileStore);
  
  const updateUserName = (newName: string) => {
    const profileStore = manager.getStore('profile');
    const currentProfile = profileStore.getValue();
    profileStore.setValue({ ...currentProfile, name: newName });
  };
  
  const updateUserEmail = (newEmail: string) => {
    const profileStore = manager.getStore('profile');
    profileStore.update(current => ({ ...current, email: newEmail }));
  };
  
  return (
    <div>
      <input 
        value={profile.name}
        onChange={e => updateUserName(e.target.value)}
      />
      <input 
        value={profile.email}
        onChange={e => updateUserEmail(e.target.value)}
      />
    </div>
  );
}

API Reference

manager.getStore(storeName)

Get a typed store instance by name. This is the primary method for accessing stores.

tsx
const manager = useUserStoreManager();

// Get store instances with full type safety
const profileStore = manager.getStore('profile');     // Store<UserProfile>
const preferencesStore = manager.getStore('preferences'); // Store<UserPreferences>
const sessionStore = manager.getStore('session');     // Store<UserSession>

// Use store methods directly
const currentProfile = profileStore.getValue();
profileStore.setValue(newProfile);
profileStore.update(profile => ({ ...profile, name: 'John' }));

Store Instance Methods

Once you have a store instance, you can use these methods:

tsx
const profileStore = manager.getStore('profile');

// Get current value
const currentProfile = profileStore.getValue();

// Set new value directly
profileStore.setValue({ id: '123', name: 'John', email: 'john@example.com', role: 'user' });

// Update with function
profileStore.update(current => ({
  ...current,
  name: 'John Doe'
}));

// Subscribe to changes
const unsubscribe = profileStore.subscribe((newValue, previousValue) => {
  console.log('Profile changed:', { newValue, previousValue });
});

// Reset to initial value
profileStore.reset();

Manager Utility Methods

tsx
const manager = useUserStoreManager();

// Get manager info
const info = manager.getInfo();
console.log(info); // { name: 'User', storeCount: 3, availableStores: ['profile', 'preferences', 'session'] }

// Clear all stores (advanced use case)
manager.clear();

Advanced Patterns

Bulk Store Operations

tsx
function BulkOperations() {
  const manager = useUserStoreManager();
  
  const handleBulkUpdate = async () => {
    // Update multiple stores in sequence
    const profileStore = manager.getStore('profile');
    const preferencesStore = manager.getStore('preferences');
    const sessionStore = manager.getStore('session');
    
    profileStore.setValue({ id: '123', name: 'John Doe', email: 'john@example.com', role: 'user' });
    preferencesStore.update(current => ({ ...current, theme: 'dark' }));
    sessionStore.update(current => ({ 
      ...current, 
      isAuthenticated: true,
      lastActivity: Date.now()
    }));
  };
  
  const handleResetAll = () => {
    // Reset all stores to initial values
    const profileStore = manager.getStore('profile');
    const preferencesStore = manager.getStore('preferences');
    const sessionStore = manager.getStore('session');
    
    profileStore.reset();
    preferencesStore.reset();
    sessionStore.reset();
  };
  
  return (
    <div>
      <button onClick={handleBulkUpdate}>Update All</button>
      <button onClick={handleResetAll}>Reset All</button>
    </div>
  );
}

Conditional Store Updates

tsx
function ConditionalUpdates() {
  const manager = useUserStoreManager();
  
  const updateProfileIfValid = (newProfile: UserProfile) => {
    const profileStore = manager.getStore('profile');
    const currentProfile = profileStore.getValue();
    
    // Only update if profile is different
    if (JSON.stringify(currentProfile) !== JSON.stringify(newProfile)) {
      profileStore.setValue(newProfile);
    }
  };
  
  const updatePreferencesIfAllowed = (newPreferences: UserPreferences) => {
    const profileStore = manager.getStore('profile');
    const preferencesStore = manager.getStore('preferences');
    const profile = profileStore.getValue();
    
    // Only update if user has permission
    if (profile.role === 'admin') {
      preferencesStore.setValue(newPreferences);
    }
  };
  
  return (
    <div>
      {/* Component JSX */}
    </div>
  );
}

Store Manager with Validation

tsx
function ValidatedStoreManager() {
  const manager = useUserStoreManager();
  
  const updateProfileWithValidation = (updates: Partial<UserProfile>) => {
    const profileStore = manager.getStore('profile');
    const currentProfile = profileStore.getValue();
    const newProfile = { ...currentProfile, ...updates };
    
    // Validate before updating
    if (isValidProfile(newProfile)) {
      profileStore.setValue(newProfile);
      return { success: true };
    } else {
      return { success: false, error: 'Invalid profile data' };
    }
  };
  
  const updatePreferencesWithDefaults = (preferences: Partial<UserPreferences>) => {
    const preferencesStore = manager.getStore('preferences');
    preferencesStore.update(current => ({
      // Apply defaults first
      theme: 'light',
      notifications: true,
      language: 'en',
      // Then apply updates
      ...current,
      ...preferences
    }));
  };
  
  return (
    <div>
      {/* Component JSX */}
    </div>
  );
}

Integration with Actions

Store Manager works seamlessly with Action Context for complex business logic:

tsx
// Action handler using Store Manager
function UserActions() {
  const manager = useUserStoreManager();
  
  useEventActionHandler('updateUserProfile', async (payload) => {
    const profileStore = manager.getStore('profile');
    const preferencesStore = manager.getStore('preferences');
    const currentProfile = profileStore.getValue();
    
    // Business logic
    const updatedProfile = await processUserUpdate(currentProfile, payload);
    
    // Update stores
    profileStore.setValue(updatedProfile);
    preferencesStore.update(preferences => ({
      ...preferences,
      language: updatedProfile.role === 'admin' ? 'en' : preferences.language
    }));
  });
  
  return null; // This is a logic-only component
}

Performance Considerations

Batch Updates

tsx
function OptimizedUpdates() {
  const manager = useUserStoreManager();
  
  const handleBatchUpdate = () => {
    // These updates happen in sequence but are optimized
    React.unstable_batchedUpdates(() => {
      const profileStore = manager.getStore('profile');
      const preferencesStore = manager.getStore('preferences');
      const sessionStore = manager.getStore('session');
      
      profileStore.setValue(newProfile);
      preferencesStore.setValue(newPreferences);
      sessionStore.setValue(newSession);
    });
  };
  
  return (
    <button onClick={handleBatchUpdate}>
      Update Multiple Stores
    </button>
  );
}

Memoized Updates

tsx
function MemoizedUpdates() {
  const manager = useUserStoreManager();
  
  const updateProfileMemoized = useCallback((updates: Partial<UserProfile>) => {
    const profileStore = manager.getStore('profile');
    profileStore.update(current => ({ ...current, ...updates }));
  }, [manager]);
  
  const resetProfileMemoized = useCallback(() => {
    const profileStore = manager.getStore('profile');
    profileStore.reset();
  }, [manager]);
  
  return (
    <UserForm 
      onUpdate={updateProfileMemoized}
      onReset={resetProfileMemoized}
    />
  );
}

Error Handling

Safe Store Operations

tsx
function SafeStoreManager() {
  const manager = useUserStoreManager();
  
  const safeUpdateStore = <K extends keyof UserStores>(
    storeName: K, 
    value: UserStores[K]
  ) => {
    try {
      const store = manager.getStore(storeName);
      store.setValue(value);
      return { success: true };
    } catch (error) {
      console.error(`Failed to update store ${String(storeName)}:`, error);
      return { success: false, error };
    }
  };
  
  const safeGetStoreValue = <K extends keyof UserStores>(storeName: K) => {
    try {
      const store = manager.getStore(storeName);
      return { success: true, value: store.getValue() };
    } catch (error) {
      console.error(`Failed to get store ${String(storeName)}:`, error);
      return { success: false, error };
    }
  };
  
  return (
    <div>
      {/* Component JSX */}
    </div>
  );
}

TypeScript Support

Store Manager provides full type safety:

tsx
interface UserProfile {
  id: string;
  name: string;
  email: string;
  role: 'admin' | 'user' | 'guest';
}

interface UserPreferences {
  theme: 'light' | 'dark';
  language: 'en' | 'ko' | 'ja' | 'zh';
  notifications: boolean;
}

interface UserSession {
  isAuthenticated: boolean;
  permissions: string[];
  lastActivity: number;
}

const {
  useStoreManager: useUserStoreManager
} = createStoreContext('User', {
  profile: {
    initialValue: { id: '', name: '', email: '', role: 'guest' as const },
    strategy: 'shallow' as const
  },
  preferences: {
    initialValue: { theme: 'light' as const, language: 'en', notifications: true },
    strategy: 'shallow' as const
  },
  session: {
    initialValue: { isAuthenticated: false, permissions: [], lastActivity: 0 },
    strategy: 'shallow' as const
  }
});

function TypeSafeComponent() {
  const manager = useUserStoreManager();
  
  // TypeScript knows the exact type of each store
  const updateProfile = (profile: UserProfile) => {
    const profileStore = manager.getStore('profile'); // Returns Store<UserProfile>
    profileStore.setValue(profile); // ✅ Type-safe
    // profileStore.setValue('invalid'); // ❌ TypeScript error
  };
  
  const getProfileData = () => {
    const profileStore = manager.getStore('profile'); // Returns Store<UserProfile>
    return profileStore.getValue(); // Returns UserProfile
  };
  
  return <div>{/* Component JSX */}</div>;
}

Best Practices

1. Use Functional Updates for Complex State

tsx
const manager = useUserStoreManager();

// ✅ Good: Functional update
const sessionStore = manager.getStore('session');
sessionStore.update(session => ({
  ...session,
  permissions: session.permissions.includes('read') 
    ? session.permissions 
    : [...session.permissions, 'read']
}));

// ❌ Avoid: Reading current state separately
const session = sessionStore.getValue();
sessionStore.setValue({
  ...session,
  permissions: [...session.permissions, 'read']
});

2. Combine with useCallback for Performance

tsx
const manager = useUserStoreManager();

const updateProfileName = useCallback((name: string) => {
  const profileStore = manager.getStore('profile');
  profileStore.update(profile => ({ ...profile, name }));
}, [manager]);
tsx
const manager = useUserStoreManager();

const handleUserLogin = useCallback(async (credentials) => {
  const user = await login(credentials);
  
  // Update related stores together
  const profileStore = manager.getStore('profile');
  const sessionStore = manager.getStore('session');
  
  profileStore.setValue({ id: user.id, name: user.name, email: user.email, role: user.role });
  sessionStore.update(session => ({
    ...session,
    isAuthenticated: true,
    lastActivity: Date.now(),
    permissions: user.permissions
  }));
}, [manager]);

4. Prefer Direct Store Access over useStore Hook

tsx
// ✅ Good: Using manager for multiple operations
const manager = useUserStoreManager();
const profileStore = manager.getStore('profile');
const preferencesStore = manager.getStore('preferences');

// Perform multiple operations efficiently
profileStore.setValue(newProfile);
preferencesStore.update(preferences => ({ ...preferences, language: 'en' }));

// ❌ Less efficient: Multiple hook calls
const profileStore = useUserStore('profile');
const preferencesStore = useUserStore('preferences');

Real-World Examples

When to Use Store Manager

Use Store Manager When:

  • Multiple Store Operations: You need to update multiple stores in a single function
  • Advanced Store Logic: Complex state manipulation requiring direct store access
  • Performance Optimization: Batch operations or avoiding multiple hook calls
  • Action Handlers: Business logic that spans multiple stores
  • Custom Store Utilities: Building reusable store manipulation functions

Use Regular Hooks When:

  • Simple State Access: Just reading or updating a single store
  • Component Rendering: Using useStoreValue for reactive UI updates
  • Basic Operations: Simple setValue/getValue operations

Released under the Apache-2.0 License.