Skip to main content

Building an Admin Dashboard with SoulState

Admin dashboards are complex applications with numerous data tables, forms, real-time updates, and intricate user interactions. SoulState's performance-first architecture and modular design make it ideal for managing state in these demanding environments.

Architecture Overview

Modular Store Structure

src/stores/
├── userStore.ts # User management
├── productStore.ts # Product catalog
├── orderStore.ts # Order processing
├── uiStore.ts # Theme, sidebar, notifications
└── index.ts # Combined exports

1. Domain-Specific Stores

Create focused stores for each business domain to maintain separation of concerns and optimize performance.

User Management Store

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

export interface User {
id: string;
name: string;
email: string;
role: 'admin' | 'editor' | 'viewer';
status: 'active' | 'inactive';
lastLogin: Date;
}

export interface UserState {
users: User[];
selectedUser: User | null;
loading: boolean;
error: string | null;
filters: {
role: string;
status: string;
search: string;
};
}

export const userStore = createStore<UserState>({
users: [],
selectedUser: null,
loading: false,
error: null,
filters: {
role: '',
status: '',
search: ''
}
});

export const userActions = {
// Async actions
fetchUsers: async () => {
userStore.set({ loading: true, error: null });
try {
const response = await fetch('/api/users');
const users = await response.json();
userStore.set({ users, loading: false });
} catch (error) {
userStore.set({ error: 'Failed to fetch users', loading: false });
}
},

// Sync actions
setSelectedUser: (user: User | null) => {
userStore.set({ selectedUser: user });
},

updateUser: (userId: string, updates: Partial<User>) => {
userStore.set(state => ({
users: state.users.map(user =>
user.id === userId ? { ...user, ...updates } : user
)
}));
},

setFilters: (filters: Partial<UserState['filters']>) => {
userStore.set(state => ({
filters: { ...state.filters, ...filters }
}));
}
};

Product Catalog Store

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

export interface Product {
id: string;
name: string;
price: number;
stock: number;
category: string;
status: 'active' | 'archived';
}

export const productStore = createStore({
products: [] as Product[],
loading: false,
categories: [] as string[]
});

export const productActions = {
fetchProducts: async (category?: string) => {
productStore.set({ loading: true });
const url = category ? `/api/products?category=${category}` : '/api/products';
const response = await fetch(url);
const products = await response.json();
productStore.set({ products, loading: false });
},

updateStock: (productId: string, newStock: number) => {
productStore.set(state => ({
products: state.products.map(product =>
product.id === productId ? { ...product, stock: newStock } : product
)
}));
}
};

2. Efficient Data Tables with Surgical Selectors

Use precise selectors to ensure components only re-render when their specific data changes.

User Table Component

// components/UserTable.tsx
import { useStore } from 'soulstate/react';
import { userStore, userActions } from '../stores/userStore';
import { shallow } from 'soulstate/utils';

export function UserTable() {
// Subscribe to filtered users and loading state
const { filteredUsers, loading } = useStore(
userStore,
(state) => {
let users = state.users;

// Apply filters
if (state.filters.role) {
users = users.filter(user => user.role === state.filters.role);
}
if (state.filters.status) {
users = users.filter(user => user.status === state.filters.status);
}
if (state.filters.search) {
const searchLower = state.filters.search.toLowerCase();
users = users.filter(user =>
user.name.toLowerCase().includes(searchLower) ||
user.email.toLowerCase().includes(searchLower)
);
}

return { filteredUsers: users, loading: state.loading };
},
shallow // Use shallow comparison for object selection
);

if (loading) {
return <div className="loading">Loading users...</div>;
}

return (
<table className="user-table">
<thead>
<tr>
<th>Name</th>
<th>Email</th>
<th>Role</th>
<th>Status</th>
<th>Last Login</th>
</tr>
</thead>
<tbody>
{filteredUsers.map(user => (
<UserTableRow key={user.id} userId={user.id} />
))}
</tbody>
</table>
);
}

// Individual row component for optimal performance
function UserTableRow({ userId }: { userId: string }) {
// Each row subscribes only to its specific user data
const user = useStore(
userStore,
(state) => state.users.find(u => u.id === userId)
);

if (!user) return null;

return (
<tr>
<td>{user.name}</td>
<td>{user.email}</td>
<td>
<select
value={user.role}
onChange={(e) => userActions.updateUser(userId, { role: e.target.value as any })}
>
<option value="admin">Admin</option>
<option value="editor">Editor</option>
<option value="viewer">Viewer</option>
</select>
</td>
<td>
<span className={`status-badge ${user.status}`}>
{user.status}
</span>
</td>
<td>{new Date(user.lastLogin).toLocaleDateString()}</td>
</tr>
);
}

3. Real-time Updates with Store Subscriptions

Integrate WebSocket connections for live data updates.

// services/websocketService.ts
import { userStore, userActions } from '../stores/userStore';
import { productStore, productActions } from '../stores/productStore';

export class WebSocketService {
private ws: WebSocket | null = null;

connect() {
this.ws = new WebSocket('ws://localhost:8080/realtime');

this.ws.onmessage = (event) => {
const data = JSON.parse(event.data);

switch (data.type) {
case 'USER_UPDATED':
userActions.updateUser(data.userId, data.updates);
break;
case 'PRODUCT_STOCK_UPDATED':
productActions.updateStock(data.productId, data.stock);
break;
case 'NEW_ORDER':
// Handle new order notification
break;
}
};

this.ws.onclose = () => {
// Attempt reconnection
setTimeout(() => this.connect(), 5000);
};
}

disconnect() {
this.ws?.close();
}
}

export const websocketService = new WebSocketService();

4. Global UI State Management

Manage theme, sidebar, and notifications in a dedicated UI store.

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

export interface Notification {
id: string;
type: 'success' | 'error' | 'warning' | 'info';
title: string;
message: string;
timestamp: Date;
}

export interface UIState {
theme: 'light' | 'dark';
sidebarOpen: boolean;
currentPage: string;
notifications: Notification[];
modals: {
userCreate: boolean;
userEdit: boolean;
productImport: boolean;
};
}

export const uiStore = createStore<UIState>({
theme: 'light',
sidebarOpen: true,
currentPage: 'dashboard',
notifications: [],
modals: {
userCreate: false,
userEdit: false,
productImport: false
}
});

export const uiActions = {
toggleTheme: () => {
uiStore.set(state => ({
theme: state.theme === 'light' ? 'dark' : 'light'
}));
},

toggleSidebar: () => {
uiStore.set(state => ({ sidebarOpen: !state.sidebarOpen }));
},

addNotification: (notification: Omit<Notification, 'id' | 'timestamp'>) => {
const newNotification: Notification = {
...notification,
id: Math.random().toString(36).substr(2, 9),
timestamp: new Date()
};

uiStore.set(state => ({
notifications: [...state.notifications, newNotification]
}));

// Auto-remove after 5 seconds
setTimeout(() => {
uiActions.removeNotification(newNotification.id);
}, 5000);
},

removeNotification: (id: string) => {
uiStore.set(state => ({
notifications: state.notifications.filter(n => n.id !== id)
}));
},

openModal: (modal: keyof UIState['modals']) => {
uiStore.set(state => ({
modals: { ...state.modals, [modal]: true }
}));
},

closeModal: (modal: keyof UIState['modals']) => {
uiStore.set(state => ({
modals: { ...state.modals, [modal]: false }
}));
}
};

5. Performance Optimizations

Memoized Selectors for Complex Data

// stores/selectors.ts
import { userStore } from './userStore';

// Memoized selector for user statistics
export const getUserStats = () => {
const users = userStore.get().users;

return {
total: users.length,
active: users.filter(u => u.status === 'active').length,
admins: users.filter(u => u.role === 'admin').length,
editors: users.filter(u => u.role === 'editor').length
};
};

// Component using memoized selector
export function UserStats() {
const stats = useStore(userStore, (state) => {
const users = state.users;
return {
total: users.length,
active: users.filter(u => u.status === 'active').length,
admins: users.filter(u => u.role === 'admin').length
};
});

return (
<div className="stats-grid">
<div className="stat-card">
<h3>Total Users</h3>
<span className="stat-value">{stats.total}</span>
</div>
<div className="stat-card">
<h3>Active Users</h3>
<span className="stat-value">{stats.active}</span>
</div>
<div className="stat-card">
<h3>Admins</h3>
<span className="stat-value">{stats.admins}</span>
</div>
</div>
);
}

Batch Updates for Better Performance

// Optimized batch update example
export const userActions = {
bulkUpdateUsers: (updates: Array<{ userId: string; updates: Partial<User> }>) => {
// Single set call for multiple updates
userStore.set(state => ({
users: state.users.map(user => {
const update = updates.find(u => u.userId === user.id);
return update ? { ...user, ...update.updates } : user;
})
}));
},

// Batch multiple related updates
createUserWithProfile: async (userData: Omit<User, 'id'>) => {
userStore.set({ loading: true });

try {
const user = await api.createUser(userData);

// Batch both updates
userStore.set(state => ({
users: [...state.users, { ...user, id: user.id }],
loading: false
}));

uiActions.addNotification({
type: 'success',
title: 'User Created',
message: `User ${user.name} was created successfully`
});
} catch (error) {
userStore.set({ loading: false });
uiActions.addNotification({
type: 'error',
title: 'Creation Failed',
message: 'Failed to create user'
});
}
}
};

6. Store Integration and Initialization

// stores/index.ts
export { userStore, userActions } from './userStore';
export { productStore, productActions } from './productStore';
export { uiStore, uiActions } from './uiStore';

// App initialization
export const initializeStores = async () => {
// Load initial data
await Promise.all([
userActions.fetchUsers(),
productActions.fetchProducts()
]);

// Start real-time services
websocketService.connect();
};

// Cleanup on app unmount
export const cleanupStores = () => {
websocketService.disconnect();
};

Performance Benefits

This architecture ensures that:

  • ✅ User table rows only re-render when their specific user data changes
  • ✅ Filter changes don't cause unnecessary re-renders in unaffected components
  • ✅ Real-time updates are efficiently propagated to relevant components
  • ✅ UI state changes are isolated and predictable
💡

Best Practices

For large admin dashboards:

  • Use multiple focused stores instead of one giant store
  • Leverage shallow equality for object selections
  • Implement row-level components for large tables
  • Batch related updates together
  • Use precise selectors to minimize re-renders