Skip to main content

Modular Stores with SoulState

As your application grows, managing all state in a single store becomes unwieldy. SoulState's modular architecture allows you to break down state into focused, domain-specific stores that work together seamlessly.

Why Modular Stores?

  • Separation of Concerns: Each store manages a specific domain
  • Better Maintainability: Smaller, focused stores are easier to understand
  • Team Scalability: Multiple teams can work on different stores
  • Performance: Smaller stores mean fewer subscribers to notify
  • Reusability: Stores can be shared across different app parts

Creating Modular Stores

Create focused stores for each business domain with clean separation between state and actions.

// stores/authStore.ts
import { createStore } from 'soulstate';

export interface User {
id: string;
name: string;
email: string;
}

export interface AuthState {
isAuthenticated: boolean;
user: User | null;
loading: boolean;
}

// Store contains only state
export const authStore = createStore<AuthState>({
isAuthenticated: false,
user: null,
loading: false
});

// Actions are separate functions
export const authActions = {
login: async (credentials: any) => {
authStore.set({ loading: true });

try {
// Simulate API call
const user = await new Promise<User>(resolve =>
setTimeout(() => resolve({
id: '123', name: 'John Doe', email: 'john@example.com'
}), 500)
);

authStore.set({
isAuthenticated: true,
user,
loading: false
});
} catch (error) {
authStore.set({ loading: false });
}
},

logout: () => {
authStore.set({
isAuthenticated: false,
user: null
});
}
};

// stores/themeStore.ts
import { createStore } from 'soulstate';

export const themeStore = createStore({
theme: 'light' as 'light' | 'dark'
});

export const themeActions = {
toggleTheme: () => {
themeStore.set(state => ({
theme: state.theme === 'light' ? 'dark' : 'light'
}));
}
};

Using Modular Stores in Components

Import and use specific stores with precise selectors for optimal performance.

import { useStore } from 'soulstate/react';
import { authStore, authActions } from '../stores/authStore';
import { themeStore, themeActions } from '../stores/themeStore';

function Header() {
// Subscribe to specific pieces of state
const isAuthenticated = useStore(authStore, (state) => state.isAuthenticated);
const user = useStore(authStore, (state) => state.user);
const theme = useStore(themeStore, (state) => state.theme);

return (
<header style={{
background: theme === 'dark' ? '#333' : '#eee',
color: theme === 'dark' ? '#eee' : '#333'
}}>
{isAuthenticated ? (
<>
<span>Welcome, {user?.name}</span>
<button onClick={authActions.logout}>Logout</button>
</>
) : (
<span>Please log in</span>
)}
<button onClick={themeActions.toggleTheme}>
Toggle Theme ({theme})
</button>
</header>
);
}

Cross-Store Communication

Method 1: Direct Action Calls

// stores/notificationStore.ts
import { createStore } from 'soulstate';

export const notificationStore = createStore({
notifications: [] as Array<{ id: number; message: string }>
});

export const notificationActions = {
addNotification: (message: string) => {
notificationStore.set(state => ({
notifications: [
...state.notifications,
{ id: Date.now(), message }
]
}));
}
};

// Modified authActions to call other store actions
export const authActions = {
login: async (credentials: any) => {
authStore.set({ loading: true });

try {
// ... login logic
authStore.set({ isAuthenticated: true, user: userData, loading: false });

// Call other store action
notificationActions.addNotification('Login successful!');
} catch (error) {
authStore.set({ loading: false });
notificationActions.addNotification('Login failed!');
}
}
};

Method 2: Store Subscriptions

// stores/analyticsStore.ts
import { createStore } from 'soulstate';
import { authStore } from './authStore';

export const analyticsStore = createStore({
loginCount: 0,
lastLogin: null as Date | null
});

// Subscribe to auth store changes
export const setupAuthAnalytics = () => {
return authStore.subscribe(
(state) => state.isAuthenticated,
(isAuthenticated, prevIsAuthenticated) => {
if (isAuthenticated && !prevIsAuthenticated) {
// Update analytics when user logs in
analyticsStore.set(state => ({
loginCount: state.loginCount + 1,
lastLogin: new Date()
}));
}
}
);
};

// Initialize in your app
// useEffect(() => {
// const unsubscribe = setupAuthAnalytics();
// return unsubscribe;
// }, []);

Method 3: Combined Actions

// stores/appActions.ts
import { authActions } from './authStore';
import { notificationActions } from './notificationStore';
import { analyticsStore } from './analyticsStore';

export const appActions = {
loginWithAnalytics: async (credentials: any) => {
try {
await authActions.login(credentials);
notificationActions.addNotification('Welcome back!');

// Update analytics directly
analyticsStore.set(state => ({
loginCount: state.loginCount + 1
}));
} catch (error) {
notificationActions.addNotification('Login failed');
throw error;
}
}
};

Store Organization Patterns

Domain-Based Structure

src/stores/
├── auth/
│ ├── authStore.ts
│ └── authActions.ts
├── users/
│ ├── userStore.ts
│ └── userActions.ts
├── ui/
│ ├── uiStore.ts
│ └── uiActions.ts
└── index.ts

Store Index for Clean Imports

// stores/index.ts
export { authStore, authActions } from './auth/authStore';
export { userStore, userActions } from './users/userStore';
export { uiStore, uiActions } from './ui/uiStore';

// Clean imports in components
import { authStore, authActions } from '../stores';

Best Practices

Keep Stores Focused

// ✅ Good: Focused store
export const cartStore = createStore({
items: [],
total: 0
});

// ❌ Avoid: Mixed concerns
export const appStore = createStore({
user: null,
products: [],
cart: [],
theme: 'light'
// Too many unrelated domains
});

Use Precise Selectors

// ✅ Good: Surgical subscriptions
function UserProfile() {
const userName = useStore(authStore, state => state.user?.name);
const theme = useStore(uiStore, state => state.theme);
// Each subscription is independent and precise

return <div className={theme}>{userName}</div>;
}
⚠️

Avoid Circular Dependencies

When stores interact, be mindful of circular dependencies. Design stores to be independent and use explicit communication patterns.

Modular Benefits

  • ✅ Better performance with focused stores
  • ✅ Clean separation of concerns
  • ✅ Easier testing and maintenance
  • ✅ Team-friendly architecture
  • ✅ Reusable across features