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