Large-Scale State Architecture with SoulState
Building large-scale applications requires careful state architecture. SoulState's performance-first design provides a solid foundation for complex, enterprise-grade applications when combined with proper architectural patterns.
Core Principles for Scale
- Modularity: Break state into small, independent stores
- Clear Ownership: Each domain owns its state and actions
- Explicit Dependencies: Clear communication between modules
- Performance First: Leverage SoulState's built-in optimizations
- Testability: Design stores and actions for isolated testing
Architectural Patterns
1. Domain-Driven Store Organization
Organize stores around business domains rather than UI components. This creates clear boundaries and makes state logic resilient to UI changes.
// Store structure by business domain
// domains/products/productStore.ts
const productStore = createStore({
// Product-specific state
});
// domains/orders/orderStore.ts
const orderStore = createStore({
// Order-specific state
});
// domains/users/userStore.ts
const userStore = createStore({
// User-specific state
});
2. Feature-Sliced Architecture
Combine domain-driven stores with feature-based organization for optimal team scalability and code ownership.
// Recommended project structure
src/
├── domains/ # Business domain stores
│ ├── products/ # Product domain logic
│ ├── orders/ # Order domain logic
│ └── users/ # User domain logic
├── features/ # Feature modules
│ ├── product-catalog/ # Uses product store
│ ├── order-management/ # Uses order store
│ └── shared/ # Cross-cutting concerns
├── app/ # App configuration and routing
└── lib/ # Utilities and services
3. Global vs Local State Strategy
Use SoulState for global state:
- Data shared across multiple components
- Server state and API responses
- Complex business logic state
- Persistent user preferences and settings
Use React local state for local state:
- Component-specific UI state
- Form inputs and temporary values
- Simple toggle states and visibility flags
- Ephemeral component state
// Example: Mixed state strategy
function UserProfile() {
// SoulState for shared data
const user = useStore(userStore, state => state.currentUser);
// React state for local UI
const [isEditing, setIsEditing] = React.useState(false);
return (
<div>
<h1>{user.name}</h1>
{isEditing && <EditForm />}
</div>
);
}
Advanced Data Management Patterns
Data Normalization with Maps
For applications with complex relational data, use Maps for optimal performance with large datasets.
// Normalized state structure
const dataStore = createStore({
// Use Maps for O(1) lookups and updates
users: new Map(), // userId -> User
products: new Map(), // productId -> Product
orders: new Map(), // orderId -> Order
// Lookup indices for relationships
userOrders: new Map(), // userId -> orderId[]
});
// Actions work with normalized data
const dataActions = {
updateUser: (userId, updates) => {
dataStore.set(state => {
const newUsers = new Map(state.users);
newUsers.set(userId, { ...newUsers.get(userId), ...updates });
return { users: newUsers };
});
}
};
Derived State with Selectors
Create reusable selectors for complex data transformations and computed values.
// Selector patterns for derived state
const userSelectors = {
// Filter and transform data
getActiveUsers: (state) =>
Array.from(state.users.values()).filter(user => user.isActive),
// Compute statistics
getUserStats: (state) => {
const users = Array.from(state.users.values());
return {
total: users.length,
active: users.filter(u => u.isActive).length,
admins: users.filter(u => u.role === 'admin').length
};
},
// Find specific entities
getUserById: (state, userId) => state.users.get(userId)
};
// Usage in components
const activeUsers = useStore(userStore, userSelectors.getActiveUsers);
Cross-Store Communication Patterns
Coordinated Actions
Coordinate complex operations that span multiple domains through dedicated action coordinators.
// Action coordination service
const orderCoordinator = {
createOrder: async (productIds, userData) => {
// 1. Validate products exist
await productActions.validateProducts(productIds);
// 2. Create order record
const order = await orderActions.createOrder({
products: productIds,
user: userData
});
// 3. Update inventory
await productActions.updateInventory(productIds);
// 4. Notify user
notificationActions.addNotification({
type: 'success',
message: 'Order created successfully'
});
return order;
}
};
Reactive Store Subscriptions
Use store subscriptions for reactive updates across domain boundaries.
// Cross-store reactivity
const setupStoreReactivity = () => {
// Subscribe to auth changes to load user profile
const unsubscribe = authStore.subscribe(
state => state.currentUser,
(user, prevUser) => {
if (user && !prevUser) {
// Automatically load profile when user logs in
userActions.loadUserProfile(user.id);
}
}
);
return unsubscribe;
};
// Initialize in app setup
// useEffect(() => {
// const unsubscribe = setupStoreReactivity();
// return unsubscribe;
// }, []);
Performance Optimization at Scale
Selective Subscription Strategy
Optimize list rendering with row-level components that subscribe only to their specific data.
// Optimized list pattern
function ProductList() {
// Subscribe only to product IDs, not the entire products map
const productIds = useStore(
productStore,
state => Array.from(state.products.keys())
);
return (
<div>
{productIds.map(id => (
<ProductListItem key={id} productId={id} />
))}
</div>
);
}
// Each item subscribes only to its own data
function ProductListItem({ productId }) {
const product = useStore(
productStore,
state => state.products.get(productId)
);
return <div>{product?.name}</div>;
}
Memoized Selectors for Expensive Computations
Memoize selectors that perform expensive operations to avoid redundant calculations.
// Memoization utility
function memoize(fn) {
const cache = new Map();
return (...args) => {
const key = JSON.stringify(args);
if (cache.has(key)) return cache.get(key);
const result = fn(...args);
cache.set(key, result);
return result;
};
}
// Memoized selectors
const productSelectors = {
getExpensiveReport: memoize((state, filters) => {
// Expensive filtering and sorting
return complexDataProcessing(state.products, filters);
})
};
Server-Side Rendering Strategy
Store Hydration Pattern
Hydrate client-side stores with server data for seamless SSR integration.
// Store hydration component
function StoreHydrator({ initialData, children }) {
React.useEffect(() => {
// Hydrate stores with server-provided data
if (initialData.user) {
userStore.set({ currentUser: initialData.user });
}
if (initialData.products) {
productStore.set({ products: new Map(initialData.products) });
}
}, [initialData]);
return <>{children}</>;
}
// Usage in app entry point
function App({ serverData }) {
return (
<StoreHydrator initialData={serverData}>
<ApplicationRouter />
</StoreHydrator>
);
}
Store Composition and Factory Patterns
Composed Stores for Complex Features
Create composed stores that coordinate multiple domains for specific features.
// Composed store for dashboard feature
const dashboardStore = createStore({
// Dashboard-specific state
isLoading: false,
lastUpdated: null
});
const dashboardActions = {
loadDashboardData: async () => {
dashboardStore.set({ isLoading: true });
// Load data from multiple domains
await Promise.all([
userActions.fetchUsers(),
productActions.fetchProducts(),
orderActions.fetchRecentOrders()
]);
dashboardStore.set({
isLoading: false,
lastUpdated: new Date()
});
}
};
Store Factory Pattern
Use factory functions for creating feature-specific stores with dependencies.
// Store factory pattern
const createFeatureStore = (dependencies) => {
const store = createStore({
// Initial state
});
const actions = {
// Actions that use dependencies
someAction: () => {
const otherState = dependencies.otherStore.get();
// Use other store's state
}
};
return { store, actions };
};
// Create feature store with dependencies
const { store, actions } = createFeatureStore({
userStore,
productStore
});
- ✅ Modular Architecture: Scales with team size and application complexity
- ✅ Performance Optimized: Built-in O(1) subscriptions and microtask batching
- ✅ Type-Safe: Full TypeScript support for large codebases
- ✅ Testable: Isolated stores and actions enable comprehensive testing
- ✅ Flexible Communication: Multiple patterns for cross-store interaction
- ✅ Memory Efficient: Structural sharing minimizes object creation
- Start with domains: Begin with domain-driven stores from day one
- Use Maps for performance: Prefer Maps over arrays for large, frequently updated datasets
- Implement surgical subscriptions: Use row-level components for large lists
- Create reusable selectors: Build selector libraries for complex data transformations
- Establish clear boundaries: Define explicit communication patterns between domains
- Monitor performance: Use React DevTools to identify unnecessary re-renders
- Plan for testing: Design stores and actions with testability in mind