By the end of this lesson, you will be able to:
ℹ️ Info Definition: State management is how your app stores, updates, and shares data between components. As apps grow from simple MVPs to complex products with millions of users, proper state architecture becomes critical for performance, maintainability, and team productivity.
Poor state management kills app scalability:
Scale Level | Users | State Challenges | Solutions Needed |
---|---|---|---|
MVP | 1K-10K | Simple local state | useState, Context |
Startup | 10K-100K | Cross-screen state | Redux, Zustand |
Scale-up | 100K-1M | Real-time sync | Optimistic updates, caching |
Enterprise | 1M+ | Performance, teams | Modular architecture, dev tools |
💡 Scale Insight: Apps that invest in state architecture early scale 5x faster than those that don't!
# Modern state management tools
npm install @reduxjs/toolkit react-redux
npm install zustand
npm install @tanstack/react-query
# Development and debugging
npm install redux-logger
npm install flipper-plugin-redux-debugger
# Real-time capabilities
npm install socket.io-client
npm install @react-native-async-storage/async-storage
# State persistence
npm install redux-persist
// utils/stateArchitecture.ts
export const StateArchitectureGuide = {
// Choose based on app complexity and team size
recommendations: {
smallApp: {
description: "1-5 screens, 1-2 developers",
solution: "React Context + useState",
when: "MVP, simple data flow"
},
mediumApp: {
description: "5-20 screens, 2-5 developers",
solution: "Zustand + React Query",
when: "Growing app, need state sharing"
},
largeApp: {
description: "20+ screens, 5+ developers",
solution: "Redux Toolkit + RTK Query",
when: "Complex state, team coordination needed"
},
enterpriseApp: {
description: "Multiple teams, microservices",
solution: "Modular Redux + State Federation",
when: "Large organization, multiple codebases"
}
}
}
// stores/appStore.ts
import { create } from 'zustand';
import { devtools, persist, subscribeWithSelector } from 'zustand/middleware';
import { immer } from 'zustand/middleware/immer';
// User interface
interface User {
id: string;
name: string;
email: string;
avatar?: string;
preferences: UserPreferences;
subscription: SubscriptionStatus;
}
interface UserPreferences {
theme: 'light' | 'dark' | 'auto';
notifications: boolean;
language: string;
}
interface SubscriptionStatus {
tier: 'free' | 'pro' | 'enterprise';
expiresAt?: Date;
features: string[];
}
// App state interface
interface AppState {
// User management
user: User | null;
isAuthenticated: boolean;
// App state
isLoading: boolean;
error: string | null;
networkStatus: 'online' | 'offline';
// Feature flags
features: Record<string, boolean>;
// Cache
cache: Record<string, any>;
lastSync: number | null;
// Actions
setUser: (user: User) => void;
updateUserPreferences: (preferences: Partial<UserPreferences>) => void;
setLoading: (loading: boolean) => void;
setError: (error: string | null) => void;
setNetworkStatus: (status: 'online' | 'offline') => void;
toggleFeature: (feature: string) => void;
updateCache: (key: string, data: any) => void;
clearCache: () => void;
logout: () => void;
}
export const useAppStore = create<AppState>()(
devtools(
persist(
subscribeWithSelector(
immer((set, get) => ({
// Initial state
user: null,
isAuthenticated: false,
isLoading: false,
error: null,
networkStatus: 'online',
features: {},
cache: {},
lastSync: null,
// Actions
setUser: (user) =>
set((state) => {
state.user = user;
state.isAuthenticated = true;
state.error = null;
}),
updateUserPreferences: (preferences) =>
set((state) => {
if (state.user) {
state.user.preferences = { ...state.user.preferences, ...preferences };
}
}),
setLoading: (loading) =>
set((state) => {
state.isLoading = loading;
}),
setError: (error) =>
set((state) => {
state.error = error;
state.isLoading = false;
}),
setNetworkStatus: (status) =>
set((state) => {
state.networkStatus = status;
}),
toggleFeature: (feature) =>
set((state) => {
state.features[feature] = !state.features[feature];
}),
updateCache: (key, data) =>
set((state) => {
state.cache[key] = {
data,
timestamp: Date.now(),
};
state.lastSync = Date.now();
}),
clearCache: () =>
set((state) => {
state.cache = {};
state.lastSync = null;
}),
logout: () =>
set((state) => {
state.user = null;
state.isAuthenticated = false;
state.cache = {};
state.error = null;
}),
}))
),
{
name: 'app-storage', // Storage key
partialize: (state) => ({
user: state.user,
isAuthenticated: state.isAuthenticated,
features: state.features,
// Don't persist cache and loading states
}),
}
),
{
name: 'app-store', // DevTools name
}
)
);
// Selectors for performance
export const useUser = () => useAppStore((state) => state.user);
export const useIsAuthenticated = () => useAppStore((state) => state.isAuthenticated);
export const useAppLoading = () => useAppStore((state) => state.isLoading);
export const useNetworkStatus = () => useAppStore((state) => state.networkStatus);
// stores/chatStore.ts - Feature-specific state
import { create } from 'zustand';
import { subscribeWithSelector } from 'zustand/middleware';
interface Message {
id: string;
text: string;
userId: string;
timestamp: Date;
type: 'text' | 'image' | 'file';
status: 'sending' | 'sent' | 'delivered' | 'read';
}
interface ChatRoom {
id: string;
name: string;
participants: string[];
lastMessage?: Message;
unreadCount: number;
}
interface ChatState {
// Data
rooms: Record<string, ChatRoom>;
messages: Record<string, Message[]>; // roomId -> messages[]
activeRoomId: string | null;
// UI state
isTyping: Record<string, string[]>; // roomId -> userIds[]
connectionStatus: 'connected' | 'connecting' | 'disconnected';
// Actions
addRoom: (room: ChatRoom) => void;
addMessage: (roomId: string, message: Message) => void;
updateMessage: (roomId: string, messageId: string, updates: Partial<Message>) => void;
markAsRead: (roomId: string) => void;
setActiveRoom: (roomId: string | null) => void;
setTyping: (roomId: string, userId: string, isTyping: boolean) => void;
setConnectionStatus: (status: 'connected' | 'connecting' | 'disconnected') => void;
// Computed
getUnreadCount: () => number;
getRoomMessages: (roomId: string) => Message[];
}
export const useChatStore = create<ChatState>()(
subscribeWithSelector((set, get) => ({
// Initial state
rooms: {},
messages: {},
activeRoomId: null,
isTyping: {},
connectionStatus: 'disconnected',
// Actions
addRoom: (room) =>
set((state) => ({
rooms: { ...state.rooms, [room.id]: room }
})),
addMessage: (roomId, message) =>
set((state) => {
const roomMessages = state.messages[roomId] || [];
const updatedMessages = [...roomMessages, message];
// Keep only last 100 messages per room for performance
const limitedMessages = updatedMessages.slice(-100);
return {
messages: {
...state.messages,
[roomId]: limitedMessages
},
rooms: {
...state.rooms,
[roomId]: {
...state.rooms[roomId],
lastMessage: message,
unreadCount: roomId === state.activeRoomId ? 0 : (state.rooms[roomId]?.unreadCount || 0) + 1
}
}
};
}),
updateMessage: (roomId, messageId, updates) =>
set((state) => {
const roomMessages = state.messages[roomId] || [];
const updatedMessages = roomMessages.map(msg =>
msg.id === messageId ? { ...msg, ...updates } : msg
);
return {
messages: {
...state.messages,
[roomId]: updatedMessages
}
};
}),
markAsRead: (roomId) =>
set((state) => ({
rooms: {
...state.rooms,
[roomId]: {
...state.rooms[roomId],
unreadCount: 0
}
}
})),
setActiveRoom: (roomId) =>
set((state) => {
// Mark as read when entering room
if (roomId && state.rooms[roomId]) {
return {
activeRoomId: roomId,
rooms: {
...state.rooms,
[roomId]: {
...state.rooms[roomId],
unreadCount: 0
}
}
};
}
return { activeRoomId: roomId };
}),
setTyping: (roomId, userId, isTyping) =>
set((state) => {
const currentTypers = state.isTyping[roomId] || [];
const updatedTypers = isTyping
? [...currentTypers.filter(id => id !== userId), userId]
: currentTypers.filter(id => id !== userId);
return {
isTyping: {
...state.isTyping,
[roomId]: updatedTypers
}
};
}),
setConnectionStatus: (status) =>
set({ connectionStatus: status }),
// Computed selectors
getUnreadCount: () => {
const { rooms } = get();
return Object.values(rooms).reduce((total, room) => total + room.unreadCount, 0);
},
getRoomMessages: (roomId) => {
const { messages } = get();
return messages[roomId] || [];
},
}))
);
// Performance-optimized selectors
export const useActiveRoom = () => useChatStore((state) =>
state.activeRoomId ? state.rooms[state.activeRoomId] : null
);
export const useUnreadCount = () => useChatStore((state) => state.getUnreadCount());
export const useRoomMessages = (roomId: string) =>
useChatStore((state) => state.getRoomMessages(roomId));
// services/realtimeSync.ts
import { useChatStore } from '../stores/chatStore';
import { useAppStore } from '../stores/appStore';
import io, { Socket } from 'socket.io-client';
class RealtimeSync {
private socket: Socket | null = null;
private reconnectAttempts = 0;
private maxReconnectAttempts = 5;
constructor() {
this.initializeConnection();
this.setupStoreSubscriptions();
}
private initializeConnection() {
const { user } = useAppStore.getState();
if (!user) return;
this.socket = io('wss://api.yourapp.com', {
auth: {
userId: user.id,
token: user.authToken,
},
transports: ['websocket'],
});
this.setupSocketListeners();
}
private setupSocketListeners() {
if (!this.socket) return;
this.socket.on('connect', () => {
console.log('Connected to real-time server');
useAppStore.getState().setNetworkStatus('online');
useChatStore.getState().setConnectionStatus('connected');
this.reconnectAttempts = 0;
});
this.socket.on('disconnect', () => {
console.log('Disconnected from real-time server');
useAppStore.getState().setNetworkStatus('offline');
useChatStore.getState().setConnectionStatus('disconnected');
this.handleReconnection();
});
this.socket.on('message:new', (message) => {
useChatStore.getState().addMessage(message.roomId, message);
});
this.socket.on('message:updated', ({ roomId, messageId, updates }) => {
useChatStore.getState().updateMessage(roomId, messageId, updates);
});
this.socket.on('typing:start', ({ roomId, userId }) => {
useChatStore.getState().setTyping(roomId, userId, true);
// Clear typing after 3 seconds
setTimeout(() => {
useChatStore.getState().setTyping(roomId, userId, false);
}, 3000);
});
this.socket.on('typing:stop', ({ roomId, userId }) => {
useChatStore.getState().setTyping(roomId, userId, false);
});
this.socket.on('user:online', ({ userId, status }) => {
// Update user online status in cache
useAppStore.getState().updateCache(`user:${userId}:status`, status);
});
}
private setupStoreSubscriptions() {
// Subscribe to state changes and sync to server
useChatStore.subscribe(
(state) => state.activeRoomId,
(roomId, previousRoomId) => {
if (previousRoomId) {
this.socket?.emit('room:leave', { roomId: previousRoomId });
}
if (roomId) {
this.socket?.emit('room:join', { roomId });
}
}
);
// Sync typing indicators
let typingTimeout: NodeJS.Timeout | null = null;
const handleTyping = (roomId: string) => {
this.socket?.emit('typing:start', { roomId });
if (typingTimeout) clearTimeout(typingTimeout);
typingTimeout = setTimeout(() => {
this.socket?.emit('typing:stop', { roomId });
}, 1000);
};
// Expose typing handler
(window as any).handleTyping = handleTyping;
}
sendMessage(roomId: string, text: string): Promise<void> {
return new Promise((resolve, reject) => {
if (!this.socket) {
reject(new Error('Not connected'));
return;
}
const tempMessage = {
id: `temp_${Date.now()}`,
text,
userId: useAppStore.getState().user!.id,
timestamp: new Date(),
type: 'text' as const,
status: 'sending' as const,
};
// Optimistic update
useChatStore.getState().addMessage(roomId, tempMessage);
this.socket.emit('message:send', {
roomId,
text,
tempId: tempMessage.id,
}, (response: { success: boolean; message?: any; error?: string }) => {
if (response.success && response.message) {
// Replace temp message with server message
useChatStore.getState().updateMessage(roomId, tempMessage.id, {
id: response.message.id,
status: 'sent',
});
resolve();
} else {
// Mark message as failed
useChatStore.getState().updateMessage(roomId, tempMessage.id, {
status: 'failed',
});
reject(new Error(response.error || 'Failed to send message'));
}
});
});
}
private handleReconnection() {
if (this.reconnectAttempts >= this.maxReconnectAttempts) {
console.log('Max reconnection attempts reached');
return;
}
const delay = Math.min(1000 * Math.pow(2, this.reconnectAttempts), 30000);
this.reconnectAttempts++;
useChatStore.getState().setConnectionStatus('connecting');
setTimeout(() => {
console.log(`Reconnection attempt ${this.reconnectAttempts}`);
this.initializeConnection();
}, delay);
}
disconnect() {
if (this.socket) {
this.socket.disconnect();
this.socket = null;
}
}
}
export const realtimeSync = new RealtimeSync();
// services/stateOptimizer.ts
import OpenAI from 'openai';
interface StateAnalysis {
performance: {
rerendersCount: number;
averageStateSize: number;
memoryUsage: number;
};
structure: {
depth: number;
unnormalizedData: string[];
circularReferences: string[];
};
patterns: {
frequentUpdates: string[];
largeObjects: string[];
derivedState: string[];
};
}
class StateOptimizer {
private openai: OpenAI;
constructor(apiKey: string) {
this.openai = new OpenAI({ apiKey });
}
async analyzeStateStructure(
stateSnapshot: Record<string, any>,
performanceMetrics: StateAnalysis['performance']
): Promise<{
issues: string[];
recommendations: string[];
optimizedStructure?: Record<string, any>;
}> {
const analysis = this.analyzeStructureIssues(stateSnapshot);
const prompt = `
Analyze this React Native app state structure for optimization:
Performance Metrics:
- Re-renders per minute: ${performanceMetrics.rerendersCount}
- Average state size: ${performanceMetrics.averageStateSize} KB
- Memory usage: ${performanceMetrics.memoryUsage} MB
Structure Issues:
- State depth: ${analysis.depth} levels
- Unnormalized data: ${analysis.unnormalizedData.join(', ')}
- Frequent updates: ${analysis.frequentUpdates.join(', ')}
- Large objects: ${analysis.largeObjects.join(', ')}
State Sample:
${JSON.stringify(stateSnapshot, null, 2).slice(0, 2000)}
Provide:
1. Performance issues identified
2. Specific optimization recommendations
3. Suggested state normalization patterns
4. Component re-render optimization strategies
Focus on mobile performance and React Native best practices.
`;
try {
const response = await this.openai.chat.completions.create({
model: 'gpt-4',
messages: [{ role: 'user', content: prompt }],
temperature: 0.3,
});
const result = JSON.parse(response.choices[0].message.content || '{}');
return {
issues: result.issues || [],
recommendations: result.recommendations || [],
optimizedStructure: result.optimizedStructure,
};
} catch (error) {
console.error('State analysis error:', error);
return {
issues: ['Unable to analyze state structure'],
recommendations: ['Consider manual state optimization'],
};
}
}
private analyzeStructureIssues(state: Record<string, any>): StateAnalysis['structure'] & StateAnalysis['patterns'] {
const issues = {
depth: this.calculateMaxDepth(state),
unnormalizedData: this.findUnnormalizedArrays(state),
circularReferences: this.findCircularReferences(state),
frequentUpdates: [], // Would need tracking data
largeObjects: this.findLargeObjects(state),
derivedState: this.findDerivedState(state),
};
return issues;
}
private calculateMaxDepth(obj: any, currentDepth = 0): number {
if (!obj || typeof obj !== 'object') return currentDepth;
let maxDepth = currentDepth;
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
const depth = this.calculateMaxDepth(obj[key], currentDepth + 1);
maxDepth = Math.max(maxDepth, depth);
}
}
return maxDepth;
}
private findUnnormalizedArrays(obj: any, path = ''): string[] {
const issues: string[] = [];
if (Array.isArray(obj) && obj.length > 0 && typeof obj[0] === 'object') {
// Check if array contains objects that could be normalized
if (obj.some(item => item.id && Object.keys(item).length > 2)) {
issues.push(`${path}: Array of ${obj.length} objects could be normalized`);
}
} else if (obj && typeof obj === 'object') {
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
issues.push(...this.findUnnormalizedArrays(obj[key], `${path}.${key}`));
}
}
}
return issues;
}
private findCircularReferences(obj: any, seen = new WeakSet(), path = ''): string[] {
const issues: string[] = [];
if (!obj || typeof obj !== 'object') return issues;
if (seen.has(obj)) {
issues.push(`${path}: Circular reference detected`);
return issues;
}
seen.add(obj);
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
issues.push(...this.findCircularReferences(obj[key], seen, `${path}.${key}`));
}
}
return issues;
}
private findLargeObjects(obj: any, path = ''): string[] {
const issues: string[] = [];
const size = JSON.stringify(obj).length;
if (size > 50000) { // 50KB threshold
issues.push(`${path}: Large object (${Math.round(size / 1024)}KB)`);
}
if (obj && typeof obj === 'object' && !Array.isArray(obj)) {
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
issues.push(...this.findLargeObjects(obj[key], `${path}.${key}`));
}
}
}
return issues;
}
private findDerivedState(obj: any, path = ''): string[] {
const issues: string[] = [];
// Look for patterns that suggest derived state
if (obj && typeof obj === 'object') {
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
// Common derived state patterns
if (key.includes('filtered') || key.includes('sorted') || key.includes('computed')) {
issues.push(`${path}.${key}: Possible derived state`);
}
issues.push(...this.findDerivedState(obj[key], `${path}.${key}`));
}
}
}
return issues;
}
generateOptimizationReport(stateSnapshot: Record<string, any>): {
summary: string;
criticalIssues: number;
recommendations: string[];
} {
const analysis = this.analyzeStructureIssues(stateSnapshot);
const criticalIssues = analysis.unnormalizedData.length +
analysis.circularReferences.length +
analysis.largeObjects.length;
const recommendations = [
...(analysis.depth > 5 ? ['Consider flattening deeply nested state'] : []),
...(analysis.unnormalizedData.length > 0 ? ['Normalize array data for better performance'] : []),
...(analysis.largeObjects.length > 0 ? ['Break down large objects into smaller pieces'] : []),
...(analysis.derivedState.length > 0 ? ['Move computed state to selectors or useMemo'] : []),
];
return {
summary: `Found ${criticalIssues} critical issues in state structure`,
criticalIssues,
recommendations,
};
}
}
export default StateOptimizer;
// hooks/useStatePerformance.ts
import { useEffect, useRef, useState } from 'react';
import { useAppStore } from '../stores/appStore';
interface PerformanceMetrics {
renderCount: number;
averageRenderTime: number;
stateUpdates: number;
memoryUsage: number;
}
export const useStatePerformance = (componentName: string) => {
const [metrics, setMetrics] = useState<PerformanceMetrics>({
renderCount: 0,
averageRenderTime: 0,
stateUpdates: 0,
memoryUsage: 0,
});
const renderStartTime = useRef<number>(0);
const renderTimes = useRef<number[]>([]);
const renderCount = useRef(0);
const stateUpdateCount = useRef(0);
// Track render performance
useEffect(() => {
renderStartTime.current = performance.now();
renderCount.current++;
return () => {
const renderTime = performance.now() - renderStartTime.current;
renderTimes.current.push(renderTime);
// Keep only last 10 render times
if (renderTimes.current.length > 10) {
renderTimes.current = renderTimes.current.slice(-10);
}
const avgRenderTime = renderTimes.current.reduce((sum, time) => sum + time, 0) / renderTimes.current.length;
setMetrics(prev => ({
...prev,
renderCount: renderCount.current,
averageRenderTime: avgRenderTime,
}));
// Report slow renders
if (renderTime > 16.67) { // Slower than 60fps
console.warn(`Slow render in ${componentName}: ${renderTime.toFixed(2)}ms`);
}
};
});
// Track state updates
useEffect(() => {
const unsubscribe = useAppStore.subscribe(() => {
stateUpdateCount.current++;
setMetrics(prev => ({
...prev,
stateUpdates: stateUpdateCount.current,
}));
});
return unsubscribe;
}, []);
// Memory usage estimation
useEffect(() => {
const checkMemory = () => {
if ('memory' in performance) {
const memInfo = (performance as any).memory;
setMetrics(prev => ({
...prev,
memoryUsage: memInfo.usedJSHeapSize / 1024 / 1024, // MB
}));
}
};
const interval = setInterval(checkMemory, 5000);
return () => clearInterval(interval);
}, []);
const resetMetrics = () => {
renderCount.current = 0;
stateUpdateCount.current = 0;
renderTimes.current = [];
setMetrics({
renderCount: 0,
averageRenderTime: 0,
stateUpdates: 0,
memoryUsage: 0,
});
};
return { metrics, resetMetrics };
};
In this lesson, you learned:
Code with AI: Try building these advanced state management features.
Prompts to try:
Proper state management is the foundation of scalable apps - invest early and reap the benefits as your app grows!