State Management
Crax includes a simple global state management solution built on top of React's useSyncExternalStore. No external dependencies, no boilerplate.
Store state lives in memory only: it persists across client-side navigation (route changes don't remount the store) but resets on a full page reload. Persistence adapters (e.g. localStorage sync) are planned but not built yet — reach for useStoreEffect in the meantime if you need to hydrate/persist manually.
Store Organization
Small projects — single file src/stores/index.ts:
// src/stores/index.ts
import { createStore } from '@crax/store'
export const themeStore = createStore<'light' | 'dark'>('light')
export const authStore = createStore({ user: null, isLoggedIn: false })
export const cartStore = createStore<{ id: number; qty: number }[]>([])
Larger projects — separate files per feature:
src/stores/
├── index.ts # Re-exports
├── auth.ts # Auth-related state
├── cart.ts # Cart state
└── theme.ts # UI preferences
Always import from @/stores/, never scatter createStore calls across page files.
Creating a Store
// src/stores/theme.ts
import { createStore } from '@crax/store'
export const themeStore = createStore<'light' | 'dark'>('light')
Using a Store
useStore returns a [state, setState] tuple, similar to useState:
import { useStore } from '@crax/store'
import { themeStore } from '@/stores/theme'
export default function ThemeToggle() {
const [theme, setTheme] = useStore(themeStore)
return (
<button onClick={() => setTheme(t => t === 'light' ? 'dark' : 'light')}>
{theme}
</button>
)
}
Always use the @/ import alias (configured in tsconfig). Never use relative paths like ../../.
Side Effects
useStoreEffect runs a side effect when store values change:
import { useStoreEffect } from '@crax/store'
import { themeStore } from '@/stores/theme'
export default function ThemePersister() {
useStoreEffect(() => {
localStorage.setItem('theme', themeStore.value)
document.body.className = themeStore.value
}, [themeStore])
return null
}
Store API
Direct access
Stores can be read and written outside of React components:
// Read
console.log(themeStore.value) // 'light'
// Write
themeStore.value = 'dark'
// Functional update
themeStore.update(t => t === 'light' ? 'dark' : 'light')
History
themeStore.value = 'dark'
themeStore.value = 'light'
console.log(themeStore.history) // ['light', 'dark']
Locking
Prevent modifications during critical operations:
const lockId = themeStore.lock()
try {
// safe operations
} finally {
themeStore.unlock(lockId)
}
Subscribing
const unsubscribe = themeStore.subscribe(() => {
console.log('theme changed:', themeStore.value)
})
// Later
unsubscribe()