Advanced State Management
Package: @coherent.js/core
Module: /src/components/component-system.js
Since: v1.1.0
Overview
Coherent.js provides a powerful, feature-rich state management system through the withState HOC and withStateUtils utilities. This guide covers advanced state management patterns including persistent state, reducers, async operations, validation, and more.
Table of Contents
- Basic State Management
- Advanced withState Options
- withStateUtils Variants
- Persistent State
- Reducer Pattern
- Async State Management
- State Validation
- Shared State
- Form State
- Loading & Error Handling
- Undo/Redo (History)
- Computed Properties
Basic State Management
Simple State
import { withState } from '@coherent.js/core';
const Counter = withState({ count: 0 })(({ state, stateUtils }) => {
const { setState } = stateUtils;
return {
div: {
'data-coherent-component': 'counter',
children: [
{ p: { text: `Count: ${state.count}` } },
{
button: {
text: '+',
onclick: () => setState({ count: state.count + 1 })
}
}
]
}
};
});
Advanced withState Options
The withState HOC accepts extensive options for fine-grained control:
const Component = withState(initialState, {
// State options
persistent: false, // Persist state across unmounts
storageKey: null, // Key for persistent storage
storage: localStorage, // Storage mechanism
// State transformation
stateTransform: null, // Transform state before injection
propName: 'state', // Prop name for state injection
actionsName: 'actions', // Prop name for action injection
// Reducers and actions
reducer: null, // State reducer function
actions: {}, // Action creators
middleware: [], // State middleware
// Performance
memoizeState: false, // Memoize state transformations
shallow: false, // Shallow state comparison
// Development
devTools: false, // Connect to dev tools
debug: false, // Debug logging
displayName: null, // Component name for debugging
// Lifecycle hooks
onStateChange: null, // Called when state changes
onMount: null, // Called when component mounts
onUnmount: null, // Called when component unmounts
// Validation
validator: null, // State validator function
// Async state
supportAsync: false // Support async state updates
})(ComponentFunction);
Example with Options
const TodoApp = withState({
todos: [],
filter: 'all'
}, {
debug: true,
persistent: true,
storageKey: 'my-todos',
validator: (state) => {
if (!Array.isArray(state.todos)) {
throw new Error('todos must be an array');
}
return true;
},
onStateChange: (newState, oldState) => {
console.log('State changed:', oldState, '->', newState);
}
})(TodoComponent);
withStateUtils Variants
The withStateUtils object provides specialized state management utilities:
1. Local State (Simple)
import { withStateUtils } from '@coherent.js/core';
const Component = withStateUtils.local({ count: 0 })(MyComponent);
2. Persistent State
Automatically saves state to localStorage:
const Component = withStateUtils.persistent(
{ user: null, preferences: {} },
'app-state' // Storage key
)(MyComponent);
Features:
- Automatic localStorage sync
- Survives page refreshes
- JSON serialization
Example:
const UserPreferences = withStateUtils.persistent({
theme: 'light',
language: 'en',
notifications: true
}, 'user-prefs')(({ state, setState }) => ({
div: {
children: [
{
select: {
value: state.theme,
onchange: (e) => setState({ theme: e.target.value }),
children: [
{ option: { value: 'light', text: 'Light' } },
{ option: { value: 'dark', text: 'Dark' } }
]
}
}
]
}
}));
3. Reducer Pattern
Redux-like state management:
const initialState = { count: 0 };
const reducer = (state, action) => {
switch (action.type) {
case 'INCREMENT':
return { ...state, count: state.count + 1 };
case 'DECREMENT':
return { ...state, count: state.count - 1 };
case 'RESET':
return { ...state, count: 0 };
default:
return state;
}
};
const actions = {
increment: (state, setState) => {
setState({ type: 'INCREMENT' });
},
decrement: (state, setState) => {
setState({ type: 'DECREMENT' });
},
reset: (state, setState) => {
setState({ type: 'RESET' });
}
};
const Counter = withStateUtils.reducer(
initialState,
reducer,
actions
)(({ state, actions }) => ({
div: {
children: [
{ p: { text: `Count: ${state.count}` } },
{ button: { text: '+', onclick: actions.increment } },
{ button: { text: '-', onclick: actions.decrement } },
{ button: { text: 'Reset', onclick: actions.reset } }
]
}
}));
4. Async State Management
Handle async operations with built-in loading/error states:
const DataFetcher = withStateUtils.async({
data: null
}, {
fetchData: async (state, setState) => {
const response = await fetch('/api/data');
const data = await response.json();
setState({ data });
}
})(({ state, actions }) => ({
div: {
children: [
{
button: {
text: 'Fetch Data',
onclick: actions.fetchData
}
},
state.data && { pre: { text: JSON.stringify(state.data, null, 2) } }
]
}
}));
5. Validated State
Enforce state validation rules:
const validator = (state) => {
if (state.age < 0 || state.age > 150) {
throw new Error('Age must be between 0 and 150');
}
if (!state.email.includes('@')) {
throw new Error('Invalid email format');
}
return true;
};
const UserForm = withStateUtils.validated({
name: '',
email: '',
age: 0
}, validator)(FormComponent);
6. Shared State
Share state across multiple components:
// Component A
const ComponentA = withStateUtils.shared({
theme: 'light'
}, 'app-theme')(({ state, setState }) => ({
div: {
children: [
{ p: { text: `Theme: ${state.theme}` } },
{
button: {
text: 'Toggle',
onclick: () => setState({
theme: state.theme === 'light' ? 'dark' : 'light'
})
}
}
]
}
}));
// Component B (shares same state)
const ComponentB = withStateUtils.shared({
theme: 'light'
}, 'app-theme')(({ state }) => ({
div: {
className: `theme-${state.theme}`,
text: 'This component shares the theme state'
}
}));
7. Form State
Specialized utilities for form handling:
const ContactForm = withStateUtils.form({
name: '',
email: '',
message: ''
})(({ state, actions }) => ({
form: {
'data-coherent-component': 'contact-form',
onsubmit: (e) => {
e.preventDefault();
const isValid = actions.validateForm((state) => {
const errors = {};
if (!state.name) errors.name = 'Name is required';
if (!state.email.includes('@')) errors.email = 'Invalid email';
return errors;
});
if (isValid) {
console.log('Form submitted:', state);
}
},
children: [
{
input: {
type: 'text',
placeholder: 'Name',
value: state.name,
oninput: (e) => actions.updateField('name', e.target.value)
}
},
{
input: {
type: 'email',
placeholder: 'Email',
value: state.email,
oninput: (e) => actions.updateField('email', e.target.value)
}
},
{
textarea: {
placeholder: 'Message',
value: state.message,
oninput: (e) => actions.updateField('message', e.target.value)
}
},
{ button: { type: 'submit', text: 'Send' } },
{ button: { type: 'button', text: 'Reset', onclick: actions.resetForm } }
]
}
}));
Form Actions:
updateField(field, value)- Update single fieldupdateMultiple(updates)- Update multiple fieldsresetForm()- Reset to initial statevalidateForm(validator)- Validate form state
8. Loading & Error Handling
Built-in loading and error state management:
const DataLoader = withStateUtils.withLoading({
users: []
})(({ state, actions }) => ({
div: {
children: [
{
button: {
text: state._loading ? 'Loading...' : 'Load Users',
disabled: state._loading,
onclick: () => actions.asyncAction(async () => {
const response = await fetch('/api/users');
const users = await response.json();
return { users };
})
}
},
state._error && {
div: {
className: 'error',
text: `Error: ${state._error.message}`
}
},
!state._loading && state.users.length > 0 && {
ul: {
children: state.users.map(user => ({
li: { text: user.name }
}))
}
}
]
}
}));
Built-in State:
_loading(Boolean) - Loading indicator_error(Error|null) - Error object
Actions:
setLoading(boolean)- Set loading statesetError(error)- Set error stateclearError()- Clear errorasyncAction(asyncFn)- Execute async function with automatic loading/error handling
9. Undo/Redo (History)
Add undo/redo functionality:
const TextEditor = withStateUtils.withHistory({
text: ''
}, 10)(({ state, actions }) => ({
div: {
children: [
{
textarea: {
value: state.present.text,
oninput: (e) => actions.updatePresent({ text: e.target.value })
}
},
{
div: {
children: [
{
button: {
text: 'Undo',
disabled: !actions.canUndo(state),
onclick: actions.undo
}
},
{
button: {
text: 'Redo',
disabled: !actions.canRedo(state),
onclick: actions.redo
}
}
]
}
}
]
}
}));
State Structure:
{
present: { /* current state */ },
past: [ /* previous states */ ],
future: [ /* undone states */ ]
}
Actions:
undo()- Undo last changeredo()- Redo last undone changeupdatePresent(newState)- Update current state (adds to history)canUndo(state)- Check if undo is availablecanRedo(state)- Check if redo is available
10. Computed Properties
Add computed/derived state:
const ShoppingCart = withStateUtils.computed({
items: [
{ id: 1, name: 'Item 1', price: 10, quantity: 2 },
{ id: 2, name: 'Item 2', price: 20, quantity: 1 }
]
}, {
// Computed properties
total: (state) => state.items.reduce((sum, item) =>
sum + (item.price * item.quantity), 0
),
itemCount: (state) => state.items.reduce((sum, item) =>
sum + item.quantity, 0
),
isEmpty: (state) => state.items.length === 0
})(({ state }) => ({
div: {
children: [
{ h2: { text: 'Shopping Cart' } },
{
ul: {
children: state.items.map(item => ({
li: { text: `${item.name} x${item.quantity} - ${item.price * item.quantity}` }
}))
}
},
{ p: { text: `Total Items: ${state.itemCount}` } },
{ p: { text: `Total Price: ${state.total}` } },
state.isEmpty && { p: { text: 'Cart is empty' } }
]
}
}));
Best Practices
1. Choose the Right Utility
// ✅ Simple local state
withStateUtils.local({ count: 0 })
// ✅ Needs persistence
withStateUtils.persistent({ user: null }, 'user-data')
// ✅ Complex state logic
withStateUtils.reducer(initialState, reducer, actions)
// ✅ Async operations
withStateUtils.async({ data: null }, { fetchData: asyncFn })
2. Immutable Updates
// ✅ Good: Immutable
setState({ todos: [...state.todos, newTodo] });
// ❌ Bad: Mutation
state.todos.push(newTodo);
setState(state);
3. Validation
// ✅ Good: Validate critical state
withStateUtils.validated(initialState, validator)
// ✅ Good: Inline validation
setState((prevState) => {
if (newValue < 0) return prevState;
return { ...prevState, value: newValue };
});
4. Debug Mode
// Enable in development
const Component = withState(initialState, {
debug: process.env.NODE_ENV === 'development'
})(MyComponent);
Migration from Simple State
Before (v1.0.x):
const Counter = withState({ count: 0 })(Component);
After (v1.1.0+):
// Still works! (backward compatible)
const Counter = withState({ count: 0 })(Component);
// Or use advanced features
const Counter = withState({ count: 0 }, {
persistent: true,
debug: true
})(Component);
See Also
- Basic State Management - Getting started
- API Reference - Complete API
- ARCHITECTURE.md - State management architecture
Version: 1.1.0+
Last Updated: October 18, 2025