Skip to main content

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

  1. Modularity: Break state into small, independent stores
  2. Clear Ownership: Each domain owns its state and actions
  3. Explicit Dependencies: Clear communication between modules
  4. Performance First: Leverage SoulState's built-in optimizations
  5. 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
});
Large-Scale Benefits
  • 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
Pro Tips for Scale
  • 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