JavaScript State Management: Redux, Zustand, and Modern Patterns
Master state management in JavaScript applications. Learn Redux, Zustand, Context API, and state management patterns for scalable applications.
State management is crucial for building scalable JavaScript applications. As applications grow in complexity, managing state becomes challenging. This guide covers various state management approaches, from simple local state to complex global state management solutions.
Fundamentals of State Management
Understanding State Types
// Different Types of State in Applications
class StateTypes {
// 1. Local Component State
static createLocalState() {
return {
// UI state - form inputs, toggles, local loading states
uiState: {
isModalOpen: false,
currentTab: 'general',
formData: {
name: '',
email: '',
message: '',
},
},
// Derived state - computed from other state
getDerivedState() {
return {
isFormValid:
this.uiState.formData.name &&
this.uiState.formData.email &&
this.uiState.formData.message,
characterCount: this.uiState.formData.message.length,
};
},
};
}
// 2. Shared Component State
static createSharedState() {
return {
// State shared between sibling components
sharedUI: {
selectedItems: [],
filters: {
category: 'all',
sortBy: 'name',
sortOrder: 'asc',
},
},
};
}
// 3. Global Application State
static createGlobalState() {
return {
// User authentication state
auth: {
user: null,
isAuthenticated: false,
permissions: [],
token: null,
},
// Application data
data: {
users: [],
posts: [],
comments: [],
cache: new Map(),
},
// Global UI state
ui: {
theme: 'light',
language: 'en',
notifications: [],
loading: false,
error: null,
},
};
}
// 4. Server State (Remote Data)
static createServerState() {
return {
// Cached server data
entities: {
users: {
byId: {},
allIds: [],
loading: false,
error: null,
lastFetch: null,
},
posts: {
byId: {},
allIds: [],
loading: false,
error: null,
lastFetch: null,
},
},
// Query metadata
queries: {
'users/all': {
data: [],
status: 'idle', // idle, loading, success, error
error: null,
lastFetch: null,
refetchOnMount: true,
},
},
};
}
}
// State Management Principles
class StateManagementPrinciples {
// Single Source of Truth
static singleSourceOfTruth = {
// Bad: Multiple sources of truth
badExample: {
userInComponent: { id: 1, name: 'John' },
userInLocalStorage: '{"id": 1, "name": "Johnny"}',
userInGlobalState: { id: 1, name: 'Jon' },
},
// Good: Single source of truth
goodExample: {
globalState: {
entities: {
users: {
1: { id: 1, name: 'John' },
},
},
},
},
};
// State is Read-Only
static immutableUpdates = {
// Bad: Direct mutation
badUpdate(state, action) {
state.count++; // Mutates state directly
state.items.push(action.payload); // Mutates array
return state;
},
// Good: Immutable updates
goodUpdate(state, action) {
return {
...state,
count: state.count + 1,
items: [...state.items, action.payload],
};
},
};
// Pure Functions for State Changes
static pureReducers = {
// Reducer function - pure, predictable
counterReducer(state = { count: 0 }, action) {
switch (action.type) {
case 'INCREMENT':
return { ...state, count: state.count + 1 };
case 'DECREMENT':
return { ...state, count: state.count - 1 };
case 'SET_COUNT':
return { ...state, count: action.payload };
default:
return state;
}
},
// Complex state updates
todosReducer(state = { items: [], filter: 'all' }, action) {
switch (action.type) {
case 'ADD_TODO':
return {
...state,
items: [
...state.items,
{
id: Date.now(),
text: action.payload,
completed: false,
createdAt: new Date().toISOString(),
},
],
};
case 'TOGGLE_TODO':
return {
...state,
items: state.items.map((item) =>
item.id === action.payload
? { ...item, completed: !item.completed }
: item
),
};
case 'SET_FILTER':
return {
...state,
filter: action.payload,
};
default:
return state;
}
},
};
}
Redux - Predictable State Container
Core Redux Implementation
// Redux Store Implementation
class ReduxStore {
constructor(reducer, initialState = {}, middleware = []) {
this.reducer = reducer;
this.state = initialState;
this.listeners = [];
this.middleware = middleware;
this.isDispatching = false;
}
getState() {
if (this.isDispatching) {
throw new Error(
'You may not call store.getState() while the reducer is executing.'
);
}
return this.state;
}
dispatch(action) {
if (this.isDispatching) {
throw new Error('Reducers may not dispatch actions.');
}
try {
this.isDispatching = true;
// Apply middleware
let dispatch = this.dispatch.bind(this);
let getState = this.getState.bind(this);
const middlewareAPI = { dispatch, getState };
const chain = this.middleware.map((middleware) =>
middleware(middlewareAPI)
);
dispatch = chain.reduceRight(
(composed, middleware) => middleware(composed),
this._dispatch.bind(this)
);
return dispatch(action);
} finally {
this.isDispatching = false;
}
}
_dispatch(action) {
if (typeof action !== 'object' || action === null) {
throw new Error('Actions must be plain objects.');
}
if (typeof action.type === 'undefined') {
throw new Error('Actions may not have an undefined "type" property.');
}
this.state = this.reducer(this.state, action);
this.listeners.forEach((listener) => listener());
return action;
}
subscribe(listener) {
if (typeof listener !== 'function') {
throw new Error('Expected listener to be a function.');
}
this.listeners.push(listener);
return () => {
const index = this.listeners.indexOf(listener);
this.listeners.splice(index, 1);
};
}
}
// Action Creators
class ActionCreators {
// Synchronous action creators
static increment() {
return { type: 'INCREMENT' };
}
static decrement() {
return { type: 'DECREMENT' };
}
static setCount(count) {
return { type: 'SET_COUNT', payload: count };
}
static addTodo(text) {
return { type: 'ADD_TODO', payload: text };
}
static toggleTodo(id) {
return { type: 'TOGGLE_TODO', payload: id };
}
// Async action creators (thunks)
static fetchUser(userId) {
return async (dispatch, getState) => {
dispatch({ type: 'FETCH_USER_START' });
try {
const response = await fetch(`/api/users/${userId}`);
const user = await response.json();
dispatch({
type: 'FETCH_USER_SUCCESS',
payload: user,
});
} catch (error) {
dispatch({
type: 'FETCH_USER_ERROR',
payload: error.message,
});
}
};
}
static updateUser(userId, updates) {
return async (dispatch, getState) => {
const state = getState();
const currentUser = state.users.byId[userId];
// Optimistic update
dispatch({
type: 'UPDATE_USER_OPTIMISTIC',
payload: { id: userId, updates },
});
try {
const response = await fetch(`/api/users/${userId}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ ...currentUser, ...updates }),
});
const updatedUser = await response.json();
dispatch({
type: 'UPDATE_USER_SUCCESS',
payload: updatedUser,
});
} catch (error) {
// Revert optimistic update
dispatch({
type: 'UPDATE_USER_ERROR',
payload: {
id: userId,
error: error.message,
previousData: currentUser,
},
});
}
};
}
}
// Complex Reducers with Normalization
class ComplexReducers {
// Root reducer combining multiple reducers
static rootReducer(state = {}, action) {
return {
auth: this.authReducer(state.auth, action),
entities: this.entitiesReducer(state.entities, action),
ui: this.uiReducer(state.ui, action),
};
}
// Authentication reducer
static authReducer(
state = {
user: null,
isAuthenticated: false,
loading: false,
error: null,
},
action
) {
switch (action.type) {
case 'LOGIN_START':
return {
...state,
loading: true,
error: null,
};
case 'LOGIN_SUCCESS':
return {
...state,
loading: false,
user: action.payload,
isAuthenticated: true,
error: null,
};
case 'LOGIN_ERROR':
return {
...state,
loading: false,
error: action.payload,
isAuthenticated: false,
};
case 'LOGOUT':
return {
user: null,
isAuthenticated: false,
loading: false,
error: null,
};
default:
return state;
}
}
// Normalized entities reducer
static entitiesReducer(
state = {
users: { byId: {}, allIds: [] },
posts: { byId: {}, allIds: [] },
},
action
) {
switch (action.type) {
case 'FETCH_USERS_SUCCESS':
const users = action.payload;
return {
...state,
users: {
byId: users.reduce(
(acc, user) => ({
...acc,
[user.id]: user,
}),
{}
),
allIds: users.map((user) => user.id),
},
};
case 'UPDATE_USER_SUCCESS':
return {
...state,
users: {
...state.users,
byId: {
...state.users.byId,
[action.payload.id]: action.payload,
},
},
};
case 'DELETE_USER_SUCCESS':
const { [action.payload]: deleted, ...remainingUsers } =
state.users.byId;
return {
...state,
users: {
byId: remainingUsers,
allIds: state.users.allIds.filter((id) => id !== action.payload),
},
};
default:
return state;
}
}
// UI state reducer
static uiReducer(
state = {
theme: 'light',
sidebarOpen: false,
notifications: [],
modal: { isOpen: false, content: null },
},
action
) {
switch (action.type) {
case 'TOGGLE_THEME':
return {
...state,
theme: state.theme === 'light' ? 'dark' : 'light',
};
case 'TOGGLE_SIDEBAR':
return {
...state,
sidebarOpen: !state.sidebarOpen,
};
case 'ADD_NOTIFICATION':
return {
...state,
notifications: [
...state.notifications,
{
id: Date.now(),
...action.payload,
timestamp: new Date().toISOString(),
},
],
};
case 'REMOVE_NOTIFICATION':
return {
...state,
notifications: state.notifications.filter(
(notification) => notification.id !== action.payload
),
};
default:
return state;
}
}
}
// Redux Middleware
class ReduxMiddleware {
// Logger middleware
static logger(store) {
return (next) => (action) => {
console.group(`Action: ${action.type}`);
console.log('Previous State:', store.getState());
console.log('Action:', action);
const result = next(action);
console.log('Next State:', store.getState());
console.groupEnd();
return result;
};
}
// Thunk middleware for async actions
static thunk(store) {
return (next) => (action) => {
if (typeof action === 'function') {
return action(store.dispatch, store.getState);
}
return next(action);
};
}
// Error handling middleware
static errorHandler(store) {
return (next) => (action) => {
try {
return next(action);
} catch (error) {
console.error('Redux Error:', error);
// Dispatch error action
store.dispatch({
type: 'GLOBAL_ERROR',
payload: {
message: error.message,
stack: error.stack,
action,
},
});
throw error;
}
};
}
// API middleware
static api(store) {
return (next) => (action) => {
if (!action.meta || !action.meta.api) {
return next(action);
}
const { url, method = 'GET', data } = action.meta.api;
const { type } = action;
// Dispatch loading action
store.dispatch({ type: `${type}_START` });
return fetch(url, {
method,
headers: { 'Content-Type': 'application/json' },
body: data ? JSON.stringify(data) : undefined,
})
.then((response) => response.json())
.then((result) => {
store.dispatch({
type: `${type}_SUCCESS`,
payload: result,
});
return result;
})
.catch((error) => {
store.dispatch({
type: `${type}_ERROR`,
payload: error.message,
});
throw error;
});
};
}
}
// Selectors for efficient state access
class Selectors {
// Basic selectors
static getAuth = (state) => state.auth;
static getUser = (state) => state.auth.user;
static isAuthenticated = (state) => state.auth.isAuthenticated;
// Entity selectors
static getUsers = (state) => state.entities.users.byId;
static getUserById = (state, userId) => state.entities.users.byId[userId];
static getAllUsers = (state) => {
const { byId, allIds } = state.entities.users;
return allIds.map((id) => byId[id]);
};
// Memoized selectors (reselect pattern)
static createMemoizedSelector(dependencies, resultFunc) {
let lastArgs = null;
let lastResult = null;
return (state) => {
const args = dependencies.map((dep) => dep(state));
if (lastArgs === null || !this.areArgsEqual(args, lastArgs)) {
lastArgs = args;
lastResult = resultFunc(...args);
}
return lastResult;
};
}
static areArgsEqual(args1, args2) {
if (args1.length !== args2.length) return false;
for (let i = 0; i < args1.length; i++) {
if (args1[i] !== args2[i]) return false;
}
return true;
}
// Computed selectors
static getActiveUsers = this.createMemoizedSelector(
[this.getAllUsers],
(users) => users.filter((user) => user.active)
);
static getUsersByRole = this.createMemoizedSelector(
[this.getAllUsers],
(users) => {
return users.reduce((acc, user) => {
if (!acc[user.role]) acc[user.role] = [];
acc[user.role].push(user);
return acc;
}, {});
}
);
}
// Usage Example
const store = new ReduxStore(
ComplexReducers.rootReducer,
{
auth: { user: null, isAuthenticated: false, loading: false, error: null },
entities: {
users: { byId: {}, allIds: [] },
posts: { byId: {}, allIds: [] },
},
ui: {
theme: 'light',
sidebarOpen: false,
notifications: [],
modal: { isOpen: false, content: null },
},
},
[
ReduxMiddleware.errorHandler,
ReduxMiddleware.logger,
ReduxMiddleware.thunk,
ReduxMiddleware.api,
]
);
// Subscribe to store changes
const unsubscribe = store.subscribe(() => {
const state = store.getState();
console.log('State updated:', state);
});
// Dispatch actions
store.dispatch(ActionCreators.increment());
store.dispatch(ActionCreators.addTodo('Learn Redux'));
store.dispatch(ActionCreators.fetchUser(1));
Zustand - Simple State Management
Zustand Implementation
// Zustand-like implementation
class ZustandStore {
constructor(createState) {
this.state = {};
this.listeners = new Set();
this.version = 0;
const setState = (partial, replace = false) => {
this.version++;
const nextState =
typeof partial === 'function' ? partial(this.state) : partial;
this.state = replace ? nextState : { ...this.state, ...nextState };
this.listeners.forEach((listener) => listener(this.state, this.state));
};
const getState = () => this.state;
const subscribe = (listener) => {
this.listeners.add(listener);
return () => this.listeners.delete(listener);
};
const destroy = () => {
this.listeners.clear();
};
this.state = createState(setState, getState, {
setState,
getState,
subscribe,
destroy,
});
}
getState() {
return this.state;
}
setState(partial, replace = false) {
this.version++;
const nextState =
typeof partial === 'function' ? partial(this.state) : partial;
this.state = replace ? nextState : { ...this.state, ...nextState };
this.listeners.forEach((listener) => listener(this.state, this.state));
}
subscribe(listener) {
this.listeners.add(listener);
return () => this.listeners.delete(listener);
}
destroy() {
this.listeners.clear();
}
}
// Create Zustand store
function createStore(createState) {
return new ZustandStore(createState);
}
// Store Examples
class ZustandExamples {
// Simple counter store
static counterStore = createStore((set, get) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
decrement: () => set((state) => ({ count: state.count - 1 })),
reset: () => set({ count: 0 }),
incrementBy: (amount) => set((state) => ({ count: state.count + amount })),
}));
// Todo store with complex operations
static todoStore = createStore((set, get) => ({
todos: [],
filter: 'all',
addTodo: (text) =>
set((state) => ({
todos: [
...state.todos,
{
id: Date.now(),
text,
completed: false,
createdAt: new Date().toISOString(),
},
],
})),
toggleTodo: (id) =>
set((state) => ({
todos: state.todos.map((todo) =>
todo.id === id ? { ...todo, completed: !todo.completed } : todo
),
})),
deleteTodo: (id) =>
set((state) => ({
todos: state.todos.filter((todo) => todo.id !== id),
})),
updateTodo: (id, updates) =>
set((state) => ({
todos: state.todos.map((todo) =>
todo.id === id ? { ...todo, ...updates } : todo
),
})),
setFilter: (filter) => set({ filter }),
clearCompleted: () =>
set((state) => ({
todos: state.todos.filter((todo) => !todo.completed),
})),
// Computed values
get filteredTodos() {
const { todos, filter } = get();
switch (filter) {
case 'active':
return todos.filter((todo) => !todo.completed);
case 'completed':
return todos.filter((todo) => todo.completed);
default:
return todos;
}
},
get stats() {
const todos = get().todos;
return {
total: todos.length,
completed: todos.filter((todo) => todo.completed).length,
active: todos.filter((todo) => !todo.completed).length,
};
},
}));
// User management store with API integration
static userStore = createStore((set, get) => ({
users: [],
currentUser: null,
loading: false,
error: null,
setLoading: (loading) => set({ loading }),
setError: (error) => set({ error }),
fetchUsers: async () => {
set({ loading: true, error: null });
try {
const response = await fetch('/api/users');
const users = await response.json();
set({ users, loading: false });
} catch (error) {
set({ error: error.message, loading: false });
}
},
createUser: async (userData) => {
set({ loading: true, error: null });
try {
const response = await fetch('/api/users', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(userData),
});
const newUser = await response.json();
set((state) => ({
users: [...state.users, newUser],
loading: false,
}));
return newUser;
} catch (error) {
set({ error: error.message, loading: false });
throw error;
}
},
updateUser: async (id, updates) => {
const { users } = get();
const userIndex = users.findIndex((user) => user.id === id);
if (userIndex === -1) {
throw new Error('User not found');
}
// Optimistic update
const updatedUsers = [...users];
updatedUsers[userIndex] = { ...updatedUsers[userIndex], ...updates };
set({ users: updatedUsers });
try {
const response = await fetch(`/api/users/${id}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(updates),
});
const updatedUser = await response.json();
set((state) => ({
users: state.users.map((user) =>
user.id === id ? updatedUser : user
),
}));
return updatedUser;
} catch (error) {
// Revert optimistic update
set({ users });
set({ error: error.message });
throw error;
}
},
deleteUser: async (id) => {
const { users } = get();
// Optimistic update
set({ users: users.filter((user) => user.id !== id) });
try {
await fetch(`/api/users/${id}`, { method: 'DELETE' });
} catch (error) {
// Revert optimistic update
set({ users });
set({ error: error.message });
throw error;
}
},
setCurrentUser: (user) => set({ currentUser: user }),
logout: () => set({ currentUser: null }),
}));
// Theme and UI store
static uiStore = createStore((set, get) => ({
theme: 'light',
sidebarOpen: false,
language: 'en',
notifications: [],
toggleTheme: () =>
set((state) => ({
theme: state.theme === 'light' ? 'dark' : 'light',
})),
setTheme: (theme) => set({ theme }),
toggleSidebar: () =>
set((state) => ({
sidebarOpen: !state.sidebarOpen,
})),
setSidebarOpen: (open) => set({ sidebarOpen: open }),
setLanguage: (language) => set({ language }),
addNotification: (notification) =>
set((state) => ({
notifications: [
...state.notifications,
{
id: Date.now(),
timestamp: new Date().toISOString(),
...notification,
},
],
})),
removeNotification: (id) =>
set((state) => ({
notifications: state.notifications.filter((n) => n.id !== id),
})),
clearNotifications: () => set({ notifications: [] }),
// Auto-remove notifications after timeout
addTimedNotification: (notification, timeout = 5000) => {
const id = Date.now();
set((state) => ({
notifications: [
...state.notifications,
{ id, timestamp: new Date().toISOString(), ...notification },
],
}));
setTimeout(() => {
set((state) => ({
notifications: state.notifications.filter((n) => n.id !== id),
}));
}, timeout);
return id;
},
}));
}
// Store Composition and Middleware
class ZustandMiddleware {
// Persistence middleware
static persist(config, options = {}) {
const {
name,
storage = localStorage,
serialize = JSON.stringify,
deserialize = JSON.parse,
partialize = (state) => state,
} = options;
return (set, get, api) => {
const initialState = config(
(...args) => {
set(...args);
const state = partialize(get());
storage.setItem(name, serialize(state));
},
get,
api
);
// Load persisted state
try {
const persistedState = storage.getItem(name);
if (persistedState) {
const parsed = deserialize(persistedState);
return { ...initialState, ...parsed };
}
} catch (error) {
console.error('Failed to load persisted state:', error);
}
return initialState;
};
}
// Logger middleware
static logger(config, options = {}) {
const { name = 'store' } = options;
return (set, get, api) => {
const loggedSet = (...args) => {
const prevState = get();
set(...args);
const nextState = get();
console.group(`${name} state change`);
console.log('Previous state:', prevState);
console.log('Arguments:', args);
console.log('Next state:', nextState);
console.groupEnd();
};
return config(loggedSet, get, api);
};
}
// DevTools middleware
static devtools(config, options = {}) {
const { name = 'store', serialize = true } = options;
return (set, get, api) => {
let isRecording = true;
const devtoolsSet = (...args) => {
if (isRecording && window.__REDUX_DEVTOOLS_EXTENSION__) {
const prevState = get();
set(...args);
const nextState = get();
window.__REDUX_DEVTOOLS_EXTENSION__.send(
{
type: 'setState',
args: serialize ? JSON.stringify(args) : args,
},
nextState
);
} else {
set(...args);
}
};
const result = config(devtoolsSet, get, api);
if (window.__REDUX_DEVTOOLS_EXTENSION__) {
window.__REDUX_DEVTOOLS_EXTENSION__.init(result);
}
return result;
};
}
}
// Usage examples
const persistedTodoStore = createStore(
ZustandMiddleware.logger(
ZustandMiddleware.persist(ZustandExamples.todoStore._createState, {
name: 'todo-storage',
partialize: (state) => ({ todos: state.todos, filter: state.filter }),
}),
{ name: 'Todo Store' }
)
);
// Subscribe to store changes
const unsubscribeTodos = ZustandExamples.todoStore.subscribe((state) => {
console.log('Todo state changed:', state);
});
const unsubscribeUI = ZustandExamples.uiStore.subscribe((state) => {
console.log('UI state changed:', state);
});
// Use the stores
ZustandExamples.todoStore.getState().addTodo('Learn Zustand');
ZustandExamples.todoStore.getState().addTodo('Build an app');
ZustandExamples.uiStore.getState().toggleTheme();
ZustandExamples.uiStore.getState().addTimedNotification({
type: 'success',
message: 'Todo added successfully!',
});
console.log('Todo stats:', ZustandExamples.todoStore.getState().stats);
console.log(
'Filtered todos:',
ZustandExamples.todoStore.getState().filteredTodos
);
Context API and React State
Context API Implementation
// Context API for React-like applications
class ContextAPI {
constructor() {
this.contexts = new Map();
}
// Create a new context
createContext(defaultValue) {
const contextId = Symbol('context');
const context = {
id: contextId,
defaultValue,
providers: new Map(),
consumers: new Set(),
};
this.contexts.set(contextId, context);
return {
Provider: this.createProvider(context),
Consumer: this.createConsumer(context),
id: contextId,
};
}
createProvider(context) {
return class Provider {
constructor(value, children = []) {
this.value = value;
this.children = children;
this.id = Symbol('provider');
context.providers.set(this.id, this);
}
updateValue(newValue) {
this.value = newValue;
// Notify all consumers
context.consumers.forEach((consumer) => {
if (consumer.providerId === this.id) {
consumer.onUpdate(newValue);
}
});
}
destroy() {
context.providers.delete(this.id);
}
};
}
createConsumer(context) {
return class Consumer {
constructor(onUpdate, providerId = null) {
this.onUpdate = onUpdate;
this.providerId = providerId;
this.id = Symbol('consumer');
context.consumers.add(this);
}
getValue() {
if (this.providerId) {
const provider = context.providers.get(this.providerId);
return provider ? provider.value : context.defaultValue;
}
// Find nearest provider
const providers = Array.from(context.providers.values());
return providers.length > 0 ? providers[0].value : context.defaultValue;
}
destroy() {
context.consumers.delete(this);
}
};
}
}
// State Management with Context Pattern
class StateContextManager {
// Auth Context
static createAuthContext() {
const authContext = new ContextAPI().createContext({
user: null,
isAuthenticated: false,
login: () => {},
logout: () => {},
loading: false,
error: null,
});
class AuthProvider extends authContext.Provider {
constructor() {
super({
user: null,
isAuthenticated: false,
loading: false,
error: null,
login: this.login.bind(this),
logout: this.logout.bind(this),
register: this.register.bind(this),
resetPassword: this.resetPassword.bind(this),
});
this.loadUserFromStorage();
}
loadUserFromStorage() {
try {
const token = localStorage.getItem('authToken');
const user = localStorage.getItem('user');
if (token && user) {
this.updateValue({
...this.value,
user: JSON.parse(user),
isAuthenticated: true,
});
}
} catch (error) {
console.error('Failed to load user from storage:', error);
}
}
async login(email, password) {
this.updateValue({ ...this.value, loading: true, error: null });
try {
const response = await fetch('/api/auth/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email, password }),
});
if (!response.ok) {
throw new Error('Login failed');
}
const { user, token } = await response.json();
localStorage.setItem('authToken', token);
localStorage.setItem('user', JSON.stringify(user));
this.updateValue({
...this.value,
user,
isAuthenticated: true,
loading: false,
error: null,
});
return user;
} catch (error) {
this.updateValue({
...this.value,
loading: false,
error: error.message,
});
throw error;
}
}
async logout() {
try {
await fetch('/api/auth/logout', { method: 'POST' });
} catch (error) {
console.error('Logout error:', error);
} finally {
localStorage.removeItem('authToken');
localStorage.removeItem('user');
this.updateValue({
user: null,
isAuthenticated: false,
loading: false,
error: null,
login: this.login.bind(this),
logout: this.logout.bind(this),
register: this.register.bind(this),
resetPassword: this.resetPassword.bind(this),
});
}
}
async register(userData) {
this.updateValue({ ...this.value, loading: true, error: null });
try {
const response = await fetch('/api/auth/register', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(userData),
});
if (!response.ok) {
throw new Error('Registration failed');
}
const { user, token } = await response.json();
localStorage.setItem('authToken', token);
localStorage.setItem('user', JSON.stringify(user));
this.updateValue({
...this.value,
user,
isAuthenticated: true,
loading: false,
error: null,
});
return user;
} catch (error) {
this.updateValue({
...this.value,
loading: false,
error: error.message,
});
throw error;
}
}
async resetPassword(email) {
this.updateValue({ ...this.value, loading: true, error: null });
try {
await fetch('/api/auth/reset-password', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email }),
});
this.updateValue({
...this.value,
loading: false,
error: null,
});
} catch (error) {
this.updateValue({
...this.value,
loading: false,
error: error.message,
});
throw error;
}
}
}
return { AuthProvider, AuthConsumer: authContext.Consumer };
}
// Theme Context
static createThemeContext() {
const themeContext = new ContextAPI().createContext({
theme: 'light',
toggleTheme: () => {},
setTheme: () => {},
colors: {},
spacing: {},
});
class ThemeProvider extends themeContext.Provider {
constructor() {
const themes = {
light: {
colors: {
primary: '#007bff',
secondary: '#6c757d',
background: '#ffffff',
text: '#212529',
border: '#dee2e6',
},
spacing: {
xs: '0.25rem',
sm: '0.5rem',
md: '1rem',
lg: '1.5rem',
xl: '3rem',
},
},
dark: {
colors: {
primary: '#4dabf7',
secondary: '#adb5bd',
background: '#121212',
text: '#ffffff',
border: '#495057',
},
spacing: {
xs: '0.25rem',
sm: '0.5rem',
md: '1rem',
lg: '1.5rem',
xl: '3rem',
},
},
};
const savedTheme = localStorage.getItem('theme') || 'light';
super({
theme: savedTheme,
themes,
toggleTheme: this.toggleTheme.bind(this),
setTheme: this.setTheme.bind(this),
...themes[savedTheme],
});
}
toggleTheme() {
const newTheme = this.value.theme === 'light' ? 'dark' : 'light';
this.setTheme(newTheme);
}
setTheme(theme) {
localStorage.setItem('theme', theme);
this.updateValue({
...this.value,
theme,
...this.value.themes[theme],
});
// Apply theme to document
document.documentElement.setAttribute('data-theme', theme);
}
}
return { ThemeProvider, ThemeConsumer: themeContext.Consumer };
}
}
// Usage examples
const { AuthProvider, AuthConsumer } = StateContextManager.createAuthContext();
const { ThemeProvider, ThemeConsumer } =
StateContextManager.createThemeContext();
// Create providers
const authProvider = new AuthProvider();
const themeProvider = new ThemeProvider();
// Create consumers
const authConsumer = new AuthConsumer((authState) => {
console.log('Auth state changed:', authState);
});
const themeConsumer = new ThemeConsumer((themeState) => {
console.log('Theme state changed:', themeState);
// Apply theme changes to UI
document.body.style.backgroundColor = themeState.colors.background;
document.body.style.color = themeState.colors.text;
});
// Use the context
console.log('Current auth state:', authConsumer.getValue());
console.log('Current theme state:', themeConsumer.getValue());
// Perform actions
authProvider.value.login('user@example.com', 'password');
themeProvider.value.toggleTheme();
Conclusion
State management is a critical aspect of building scalable JavaScript applications. Choose the right approach based on your application's complexity: local state for simple scenarios, Context API for moderate complexity, and dedicated libraries like Redux or Zustand for complex applications with intricate state interactions. Remember to consider factors like predictability, debuggability, performance, and team familiarity when making your choice. Each approach has its strengths, and understanding multiple patterns will make you a more effective developer.