Back to patterns
State Management
STATE
A visual approach to debugging state management issues, tracking component re-renders, state mutations, and cache invalidation patterns.
Use Cases
- Complex state flows
- Component re-renders
- Cache invalidation
- State mutation tracking
Production Implementation
Implementation
// Real-world example: Type-safe state management with debugging and performance optimization
import { createContext, useContext, useCallback, useRef, useEffect } from 'react';
// Type definitions for state store
type Listener<T> = (state: T) => void;
type Selector<T, R> = (state: T) => R;
type Action<T> = (state: T) => Partial<T>;
type Middleware<T> = (action: Action<T>, state: T) => Promise<void> | void;
interface StoreConfig<T> {
initialState: T;
middleware?: Middleware<T>[];
persist?: boolean;
debug?: boolean;
}
class Store<T extends object> {
private state: T;
private listeners: Set<Listener<T>>;
private middleware: Middleware<T>[];
private debug: boolean;
private persist: boolean;
private updateCount: number;
private lastUpdate: number;
constructor(config: StoreConfig<T>) {
this.state = this.loadInitialState(config);
this.listeners = new Set();
this.middleware = config.middleware || [];
this.debug = config.debug || false;
this.persist = config.persist || false;
this.updateCount = 0;
this.lastUpdate = Date.now();
}
private loadInitialState(config: StoreConfig<T>): T {
if (config.persist && typeof window !== 'undefined') {
const stored = localStorage.getItem('app_state');
if (stored) {
try {
return JSON.parse(stored);
} catch (e) {
console.error('Failed to load persisted state:', e);
}
}
}
return config.initialState;
}
getState(): T {
return this.state;
}
async dispatch(action: Action<T>): Promise<void> {
const startTime = performance.now();
const prevState = { ...this.state };
try {
// Run middleware before state update
for (const middleware of this.middleware) {
await middleware(action, prevState);
}
// Update state
const update = action(prevState);
const nextState = { ...prevState, ...update };
// Validate state changes
this.validateStateUpdate(prevState, nextState);
this.state = nextState;
this.updateCount++;
this.lastUpdate = Date.now();
// Persist if enabled
if (this.persist) {
this.persistState();
}
// Notify listeners
this.notifyListeners();
// Debug logging
if (this.debug) {
this.logStateUpdate(prevState, nextState, startTime);
}
} catch (error) {
console.error('State update failed:', error);
throw error;
}
}
private validateStateUpdate(prev: T, next: T): void {
// Detect accidental mutations
if (Object.keys(next).some(key => prev[key] === next[key] && typeof prev[key] === 'object')) {
console.warn('Possible state mutation detected. State updates should be immutable.');
}
// Check for undefined values
Object.entries(next).forEach(([key, value]) => {
if (value === undefined) {
console.warn(`State key "${key}" was set to undefined. This may cause issues.`);
}
});
}
private persistState(): void {
try {
localStorage.setItem('app_state', JSON.stringify(this.state));
} catch (e) {
console.error('Failed to persist state:', e);
}
}
private logStateUpdate(prev: T, next: T, startTime: number): void {
const duration = performance.now() - startTime;
const changes = Object.keys(next).filter(key => prev[key] !== next[key]);
console.group('State Update');
console.log('Previous:', prev);
console.log('Next:', next);
console.log('Changed keys:', changes);
console.log(`Update took ${duration.toFixed(2)}ms`);
console.log(`Total updates: ${this.updateCount}`);
console.groupEnd();
}
subscribe(listener: Listener<T>): () => void {
this.listeners.add(listener);
return () => this.listeners.delete(listener);
}
private notifyListeners(): void {
for (const listener of this.listeners) {
listener(this.state);
}
}
// Debug utilities
getDebugInfo() {
return {
updateCount: this.updateCount,
lastUpdate: new Date(this.lastUpdate).toISOString(),
listenerCount: this.listeners.size,
stateSize: JSON.stringify(this.state).length,
};
}
}
// React integration
const StoreContext = createContext<Store<any> | null>(null);
export function useStore<T extends object, R>(
selector: Selector<T, R>,
equalityFn: (a: R, b: R) => boolean = Object.is
): R {
const store = useContext(StoreContext);
if (!store) throw new Error('Store not found in context');
const [state, setState] = useState(selector(store.getState()));
const prevValue = useRef(state);
useEffect(() => {
return store.subscribe((newState) => {
const newValue = selector(newState);
if (!equalityFn(prevValue.current, newValue)) {
prevValue.current = newValue;
setState(newValue);
}
});
}, [store, selector, equalityFn]);
return state;
}
// Example usage with TypeScript
interface AppState {
user: {
id: string;
name: string;
preferences: {
theme: 'light' | 'dark';
notifications: boolean;
};
} | null;
todos: Array<{
id: string;
text: string;
completed: boolean;
}>;
ui: {
sidebarOpen: boolean;
activeModal: string | null;
};
}
// Middleware example: Logger
const loggerMiddleware: Middleware<AppState> = async (action, state) => {
console.group('Action');
console.log('Previous State:', state);
console.log('Action:', action.name);
console.groupEnd();
};
// Middleware example: Analytics
const analyticsMiddleware: Middleware<AppState> = async (action, state) => {
if (action.name === 'updateUser') {
analytics.track('user_updated', {
userId: state.user?.id,
timestamp: new Date().toISOString()
});
}
};
// Create store instance
const store = new Store<AppState>({
initialState: {
user: null,
todos: [],
ui: {
sidebarOpen: false,
activeModal: null
}
},
middleware: [loggerMiddleware, analyticsMiddleware],
persist: true,
debug: process.env.NODE_ENV === 'development'
});
// Component example
function UserProfile() {
const user = useStore((state: AppState) => state.user);
const updateUser = useCallback((name: string) => {
store.dispatch((state) => ({
user: state.user ? { ...state.user, name } : null
}));
}, []);
if (!user) return <div>Please log in</div>;
return (
<div>
<h1>{user.name}</h1>
<button onClick={() => updateUser('New Name')}>
Update Name
</button>
</div>
);
}
Code Examples
Common State Management Issues
typescript
// ❌ Common state management issues
class Component {
// Issue 1: Direct state mutation
updateUser(name: string) {
this.state.user.name = name;
}
// Issue 2: No state immutability
addTodo(todo: Todo) {
this.state.todos.push(todo);
}
// Issue 3: Inconsistent updates
async fetchUser() {
const user = await api.getUser();
this.state.user = user;
// Preferences might be out of sync
}
// Issue 4: No cleanup
componentDidMount() {
window.addEventListener('resize', this.handleResize);
}
}
Common issues include direct state mutations, lack of immutability, inconsistent updates, and missing cleanup.
Robust State Management
typescript
// ✅ Robust state management
class Component {
// Immutable state updates
updateUser(name: string) {
this.setState(state => ({
user: state.user
? { ...state.user, name }
: null
}));
}
// Batch related updates
async fetchUserWithPreferences() {
const [user, preferences] = await Promise.all([
api.getUser(),
api.getUserPreferences()
]);
this.setState({
user: { ...user, preferences }
});
}
// Proper cleanup
componentDidMount() {
const handler = this.handleResize.bind(this);
window.addEventListener('resize', handler);
return () => {
window.removeEventListener('resize', handler);
};
}
// Optimized re-renders
shouldComponentUpdate(nextProps, nextState) {
return !isEqual(this.state, nextState) ||
!isEqual(this.props, nextProps);
}
}
This implementation uses immutable updates, batches related changes, includes proper cleanup, and optimizes re-renders.
Best Practices
- Use immutable state updates
- Implement proper type safety
- Batch related state changes
- Add debugging capabilities
- Include state persistence
- Optimize component re-renders
- Handle cleanup properly
Common Pitfalls
- Direct state mutations
- Missing type safety
- Inconsistent state updates
- No performance optimization
- Memory leaks from missing cleanup
- Poor error handling
- Lack of debugging tools
Related Patterns
Promise Chaining
Visualize and debug complex asynchronous flows and promise chains for structured reasoning and step-by-step task completion.
Memory Leaks 101
A systematic approach to identifying and fixing memory leaks through heap snapshot analysis, memory usage visualization, and common leak pattern detection.