A modern, type-safe state management library for React applications that combines Zustand's performance with object-oriented controller classes and modular slice architecture.
Better React State is designed for React applications that need:
- Scalable state management beyond useState/useReducer
- Async initialization with initialization order and async setup functions, ensures that stores are initialized in the correct order and time
- Object-oriented business logic organized in controller classes
- Type-safe development with full IDE navigation support
- Modular architecture with slice-based organization
- Fine-grained persistence control for complex applications
β Ideal for:
- Medium to large React applications (5+ components with shared state)
- TypeScript projects requiring strong type safety
- Applications with complex business logic that benefits from class organization
- Projects needing granular persistence control (localStorage/sessionStorage)
- Teams wanting Ctrl/Cmd+click navigation to method implementations
β Not recommended for:
- Simple applications with minimal state requirements
- Projects that prefer functional programming over object-oriented approaches
- Applications where bundle size is critical (adds Zustand dependency)
- Teams unfamiliar with TypeScript or class-based patterns
Before (Standard React):
// Traditional useState approach
const [count, setCount] = useState(0)
const [loading, setLoading] = useState(false)
const increment = () => setCount(prev => prev + 1)
const decrement = () => setCount(prev => Math.max(0, prev - 1))
const reset = () => setCount(0)After (Better React State):
// Organized in controller classes with type safety
const store = useAppStore()
const { count, loading } = store.counter.state
const { counterController } = store.counter.controllers
// Methods with full navigation support
counterController.increment() // Ctrl/Cmd+click navigates to implementation
counterController.decrement() // IDE shows method signature and docs
counterController.reset() // Type-safe with compile-time validationBefore (Context API):
// Complex context setup
const CounterContext = createContext()
const CounterProvider = ({ children }) => {
const [state, dispatch] = useReducer(counterReducer, initialState)
return <CounterContext.Provider value={{state, dispatch}}>{children}</CounterContext.Provider>
}After (Better React State):
// Simple store creation
export const useAppStore = createAppStore({
name: 'my-app',
slices: [counterSliceConfig, taskListSliceConfig],
onSave: async (state) => await saveToServer(state) // Optional server sync
})Before (Redux):
// Verbose action creators and reducers
const incrementAction = createAction('counter/increment')
const counterSlice = createSlice({
name: 'counter',
initialState: { count: 0 },
reducers: { increment: (state) => { state.count += 1 } }
})After (Better React State):
// Controller classes with business logic
export class CounterController {
constructor(
private getState: () => CounterState,
private setState: (state: Partial<CounterState>) => void
) {
this.getState = getState
this.setState = setState
}
increment = (): void => {
const currentState = this.getState()
this.setState({ count: currentState.count + 1 })
}
}// src/store/CounterStore.ts
import { BaseState } from 'better-react-state'
/**
* Counter state interface - extends BaseState for slice requirements
* BaseState provides: status, error, initialized, version
*/
export interface CounterState extends BaseState {
count: number // Primary counter value
loadingCountDown: number // UI loading countdown (example from user's code)
maxValue?: number // Optional constraint
}// src/store/CounterController.ts
import type { CounterState } from './CounterStore'
/**
* CounterController - Encapsulates all counter business logic
* Constructor injection pattern provides type-safe state access
*/
export class CounterController {
private readonly getState: () => CounterState
private readonly setState: (state: Partial<CounterState>) => void
constructor(
getState: () => CounterState,
setState: (state: Partial<CounterState>) => void
) {
if (!getState || typeof getState !== 'function') {
throw new Error('CounterController: getState must be a function')
}
this.getState = getState
this.setState = setState
}
/**
* Increment counter with safety bounds checking
* Prevents overflow beyond Number.MAX_SAFE_INTEGER
*/
increment = (): void => {
try {
const state = this.getState()
const newCount = state.count + 1
if (newCount > Number.MAX_SAFE_INTEGER) {
throw new Error('Counter would exceed maximum safe integer')
}
this.setState({ count: newCount })
} catch (error) {
console.error('CounterController.increment failed:', error)
this.setState({ error: error.message })
}
}
/**
* Decrement counter with lower bound protection
* Prevents negative values (customize as needed)
*/
decrement = (): void => {
try {
const state = this.getState()
const newCount = Math.max(0, state.count - 1)
this.setState({ count: newCount })
} catch (error) {
console.error('CounterController.decrement failed:', error)
this.setState({ error: error.message })
}
}
/**
* Reset counter to initial state
* Clears any errors and resets to zero
*/
reset = (): void => {
this.setState({
count: 0,
error: null,
loadingCountDown: 5 // Reset loading countdown
})
}
/**
* Utility method - check if counter is at maximum
*/
isAtMaximum = (): boolean => {
const state = this.getState()
return state.maxValue ? state.count >= state.maxValue : false
}
}// src/store/CounterStore.ts (continued)
import { createStoreSlice } from 'better-react-state'
import { CounterController } from './CounterController'
/**
* Controllers interface - defines all controller classes for this slice
* Enables type-safe access to business logic methods
*/
export interface CounterControllers {
counterController: CounterController
}
/**
* Initial state configuration
* Must include all BaseState properties: status, error, initialized, version
*/
const initialCounterState: CounterState = {
count: 0,
loadingCountDown: 5,
maxValue: 100,
status: {}, // Required by BaseState
error: null, // Required by BaseState
initialized: false, // Required by BaseState
version: 0 // Required by BaseState
}
/**
* Create counter slice with dependency injection pattern
* The setup function receives state management functions and returns controllers
*/
export const createCounterSlice = createStoreSlice<CounterState, CounterControllers>(
initialCounterState, // initial state
'counter', // slice name
async (_update, _get, getState, setState) => { // controllers setup function
// Initialize controller with injected state management functions
const counterController = new CounterController(getState, setState)
// Return all controllers for this slice
return { counterController }
},
{ // slice options
// Optional: Define persistence behavior
persist: {
whitelist: ['count', 'maxValue'] // Only persist these fields
// blacklist: ['loadingCountDown'] // Alternative: exclude specific fields
},
// dependencies: ['taskList'] // optional: dependencies for this slice
}
)
// Export types for use in other files
export type { CounterState, CounterControllers }// src/store/AppStore.ts
import {
createAppStore,
type SliceConfig
} from 'better-react-state'
import {
createCounterSlice,
type CounterState,
type CounterControllers
} from './CounterStore'
import {
createTaskListSlice,
type TaskListState,
type TaskListControllers
} from './TaskListStore'
/**
* Type-safe slice configurations
* Specify exact state and controller types for each slice
*/
const counterSliceConfig: SliceConfig<CounterState, CounterControllers> = {
name: 'counter',
create: createCounterSlice,
options: {
persist: { whitelist: ['count'] } // Fine-grained persistence control
}
}
const taskListSliceConfig: SliceConfig<TaskListState, TaskListControllers> = {
name: 'taskList',
create: createTaskListSlice,
options: {
persist: { whitelist: ['tasks'] },
dependencies: ['counter'] // optional: dependencies for this slice
}
}
/**
* Combined slice array for store configuration
* Add new slices here as your application grows
*/
const appSlices: SliceConfig<any, any>[] = [
counterSliceConfig,
taskListSliceConfig
]
/**
* Optional: Server-side state synchronization
* Called whenever state changes occur (debounced internally)
*/
const onSave = async (state: any) => {
try {
console.log('State saved:', state)
// await fetch('/api/save-state', { method: 'POST', body: JSON.stringify(state) })
} catch (error) {
console.error('Failed to save state to server:', error)
}
}
/**
* Create the main application store
* Combines all slices with persistence and optional server sync
*/
const store = createAppStore({
name: 'better-react-state-example', // Used for localStorage key
slices: appSlices,
onSave // Optional server synchronization
})
/**
* Typed interface for your specific application store
* Include all base properties and your specific slice types
*/
export interface TypedAppStore {
// Base store properties (required for proper functionality)
initialized: boolean // Store initialization status
isInitializing: boolean // Currently initializing flag
error: string | null // Global error state
version: number // State version for change tracking
initObject?: any // Initialization data
setup: (initObject?: any) => Promise<void> // Initialization function
// Your application-specific slices
counter: {
name: 'counter'
state: CounterState
controllers: CounterControllers
getState: () => CounterState
setState: (state: Partial<CounterState>) => void
update: () => void
setError: (error: any) => void
reset: () => void
setup: (initObject?: any) => Promise<void>
}
taskList: {
name: 'taskList'
state: TaskListState
controllers: TaskListControllers
getState: () => TaskListState
setState: (state: Partial<TaskListState>) => void
update: () => void
setError: (error: any) => void
reset: () => void
setup: (initObject?: any) => Promise<void>
}
}
/**
* Export typed store hook for use in React components
* Type assertion provides full IntelliSense and type safety
*/
export const useAppStore = store as unknown as () => TypedAppStore
// Re-export types for easy importing in components
export type { CounterState, CounterControllers }
export type { TaskListState, TaskListControllers }// src/App.tsx
import { useAppStore } from './store/AppStore'
/**
* Loading spinner with countdown (based on user's code)
* Shows initialization progress with dynamic countdown
*/
function LoadingSpinner({ count }: { count: number }) {
return (
<div className="loading-container">
<div className="loading-spinner"></div>
<h2>Initializing Better React State...</h2>
<h2>Waiting for setup function: {count} seconds left</h2>
<p>Setting up store slices and controllers</p>
<div className="loading-steps">
<div className="loading-step">β‘ Loading Counter slice</div>
<div className="loading-step">π Loading TaskList slice</div>
<div className="loading-step">ποΈ Initializing controllers</div>
<div className="loading-step">πΎ Restoring persisted state</div>
</div>
</div>
)
}
function App() {
const store = useAppStore()
console.log('π»App.tsx:25/(store):', store)
// Initialize store if not already initialized (user's pattern)
if (!store.initialized) {
store.setup()
}
// Show loading spinner while initializing
if (store.isInitializing) {
return <LoadingSpinner count={store.counter.state.loadingCountDown} />
}
// Show error if initialization failed
if (store.error) {
return (
<div className="error-container">
<h2>β Initialization Error</h2>
<p>{store.error}</p>
<button onClick={() => window.location.reload()}>
Reload Page
</button>
</div>
)
}
// Ensure store and slices are fully initialized
if (!store.initialized) {
return <LoadingSpinner count={store.counter.state.loadingCountDown} />
}
// Main application content with full type safety
return (
<div className="app-container">
<header className="app-header">
<h1>π Better React State Example</h1>
<p>A modern, type-safe state management library with controller classes</p>
</header>
<main className="app-main">
<div className="demo-section">
<Counter
state={store.counter.state}
controllers={store.counter.controllers}
update={store.counter.update}
setState={store.counter.setState}
/>
</div>
<div className="demo-section">
<TaskList
state={store.taskList.state}
controllers={store.taskList.controllers}
update={store.taskList.update}
setState={store.taskList.setState}
/>
</div>
</main>
<footer className="app-footer">
<p>Store Status: β
Initialized | Controllers: β
Ready | Persistence: β
Active</p>
</footer>
</div>
)
}
export default AppWhen implementing Better React State, you'll create the following file structure:
src/
βββ store/
β βββ AppStore.ts # Main store configuration and TypedAppStore interface
β βββ CounterStore.ts # Counter slice definition and state interface
β βββ CounterController.ts # Counter business logic class
β βββ TaskListStore.ts # Task list slice (if applicable)
β βββ TaskListController.ts # Task list business logic class
β βββ types.ts # Shared type definitions (optional)
βββ components/
β βββ Counter.tsx # Counter UI component
β βββ TaskList.tsx # Task list UI component
β βββ ...
βββ App.tsx # Main app with store initialization
βββ ...
dist/ (after npm run build)
βββ better-react-state.es.js # ES modules build output
βββ better-react-state.umd.js # UMD build output
βββ better-react-state.d.ts # TypeScript declarations
βββ ...
{
"dependencies": {
"better-react-state": "^1.0.0",
"zustand": "^4.0.0",
"react": "^18.0.0"
},
"devDependencies": {
"typescript": "^5.0.0",
"@types/react": "^18.0.0"
}
}-
TypeScript Required: While JavaScript usage is possible, the library is designed for TypeScript and loses significant value without it.
-
Zustand Dependency: Adds ~13kB to your bundle size (gzipped: ~4kB).
-
Controller Constructor Pattern: Controllers must follow the specific constructor injection pattern:
constructor( private getState: () => StateType, private setState: (state: Partial<StateType>) => void ) {}
-
BaseState Extension: All state interfaces must extend
BaseState:interface YourState extends BaseState { // Your properties... }
-
Slice Naming: Slice names must be unique across the entire application store.
-
Class-Based Controllers: If your team prefers functional programming, this library may not align with your coding style.
-
Setup Function Required: Store initialization requires calling
store.setup()before accessing slice data. -
Persistence Limitations: Persistence is localStorage/sessionStorage based - not suitable for sensitive data.
-
Single Store Pattern: Designed around one central store per application (though you can create multiple stores if needed).
-
Controller Instantiation: Controllers are created during slice setup, not per-component render.
-
State Updates: Use
setStatewith partial updates only - avoid frequent complete state replacements. -
Subscription Patterns: Components automatically subscribe to relevant state changes via Zustand's selector system.
Primary Persona: Senior Full-Stack Developer with TypeScript Expertise
β Ideal AI Use Cases:
- Building React applications with complex state management needs
- Converting existing useState/Redux applications to modern alternatives
- Creating type-safe, maintainable codebases with IDE navigation support
- Implementing modular architectures with clear separation of concerns
- Requiring fine-grained persistence control and optional server synchronization
AI Assistant Context Considerations:
-
Code Generation: When suggesting Better React State implementations, always:
- Start with state interface definitions extending BaseState
- Create controller classes with constructor injection pattern
- Use SliceConfig with specific type parameters:
SliceConfig<StateType, ControllersType> - Include proper error handling and input validation in controllers
-
Migration Assistance: When converting from other state management:
- Identify useState calls that can become controller methods
- Group related state and logic into logical slices
- Preserve existing business logic while reorganizing into classes
-
Architecture Decisions: Recommend Better React State when:
- Application has 5+ components sharing state
- Business logic complexity benefits from class organization
- TypeScript is already in use or planned
- Team values IDE navigation and compile-time safety
-
Alternative Recommendations: Suggest simpler solutions when:
- Application state is minimal (1-3 pieces of shared state)
- Team prefers functional programming patterns exclusively
- Bundle size is critical constraint
- TypeScript adoption is not feasible
Key AI Implementation Patterns:
// Always use this pattern for controller classes
export class [Domain]Controller {
constructor(
private getState: () => [Domain]State,
private setState: (state: Partial<[Domain]State>) => void
) {}
// Business logic methods with error handling
[methodName] = (): void => {
try {
const state = this.getState()
// Business logic here
this.setState({ /* updated fields */ })
} catch (error) {
console.error('[Domain]Controller.[methodName] failed:', error)
this.setState({ error: error.message })
}
}
}This library is specifically designed for AI assistants to implement robust, type-safe React state management with minimal setup complexity and maximum developer experience benefits.
- Example Application: See
/public/example/directory for complete implementation - Type Safety Guide: See
TYPING_IMPROVEMENTS.mdfor detailed TypeScript information - Migration Patterns: Contact for specific migration assistance from Redux/Context API
# Install the library
npm install better-react-state zustand
# Start with the example project
cd public/example
npm install
npm run dev
# Build your application
npm run buildMIT - Feel free to use in commercial and personal projects.