Skip to main content

Modular Store Design (Slices)

As applications grow, a single createStore call with hundreds of lines can become unmanageable. SoulState's Slice Abstraction allows you to break your state into focused, independent modules while maintaining a unified, systems-grade runtime.

Defining Slices

A slice encapsulates a specific domain of your application state and the logic that operates on it.

// features/auth/authSlice.ts
import { createSlice } from 'soulstate';

export const authSlice = createSlice({
name: 'auth',
initialState: { user: null, token: null },
reducers: {
login: (state, user) => ({ user }),
logout: () => ({ user: null, token: null })
}
});

Composing the Root Store

Slices are combined using combineSlices and then passed to createStore. This creates a single root state object where each slice's name is a top-level key.

// store.ts
import { createStore, combineSlices } from 'soulstate';
import { authSlice } from './features/auth/authSlice';
import { themeSlice } from './features/theme/themeSlice';

export const store = createStore(
combineSlices(authSlice, themeSlice)
);

// Final state structure:
// {
// auth: { user: null, token: null, login: f, logout: f },
// theme: { mode: 'dark', setTheme: f }
// }

Accessing Modular State

In components, you use the slice name to drill into the specific domain. Because of SoulState's Surgical Tracking, subscribing to s => s.auth.user will not cause re-renders if theme.mode changes.

function Profile() {
const user = useStore(store, s => s.auth.user);
return <div>{user.name}</div>;
}

Modular Actions

Actions in a slice are automatically injected into the state tree. This keeps your API discoverable and grouped by domain.

function LogoutButton() {
// Actions are stable and part of the slice state
const { logout } = useStore(store, s => s.auth);

return <button onClick={logout}>Log Out</button>;
}

Scaling Strategies

  1. Slice per Feature: Create a slice for every major feature (Auth, Settings, DataGrid, etc.).
  2. Shared Slices: Use a shared slice for cross-cutting data like notifications or configuration.
  3. Cross-Slice Logic: If an action needs to update multiple slices, define it as a standard action that calls store.setState at the root level, rather than inside a specific slice's reducers.
ℹ️

No Performance Penalty

Modularizing your store with slices has no runtime performance cost. SoulState's propagation engine treats the combined tree as a single graph, providing the same systems-grade throughput as a flat store.