#State Management
Alepha provides $atom for defining typed, validated state and useStore for consuming it in React components. State is a simple key-value store with schema validation, SSR hydration, and event-driven updates.
#Defining Atoms
Atoms are defined at the module level using $atom. Each atom has a unique name, a TypeBox schema, and an optional default value.
1import { $atom, t } from "alepha";2 3const counter = $atom({4 name: "app:counter",5 schema: t.object({6 count: t.integer(),7 }),8 default: { count: 0 },9});
Atoms must contain only serializable data. Avoid storing class instances, functions, or DOM elements.
Options:
| Option | Type | Description |
|---|---|---|
name |
string |
Unique identifier for the atom. |
schema |
TObject |
TypeBox schema for validation. |
default |
Static<T> |
Default value. Required unless schema is fully optional. |
description |
string |
Optional description for documentation. |
#useStore Hook
The useStore hook connects React components to Alepha state. It returns a [value, setValue] tuple, similar to useState.
1import { useStore } from "alepha/react"; 2 3function Counter() { 4 const [state, setState] = useStore(counter); 5 6 return ( 7 <button onClick={() => setState({ count: state.count + 1 })}> 8 Count: {state.count} 9 </button>10 );11}
When the state changes (from any source), the component re-renders automatically.
#Using with Atom
1const [value, setValue] = useStore(counter);
#Using with Store Keys
You can also access state by string key, without an atom:
1const [lang, setLang] = useStore("alepha.react.i18n.lang");
This is used internally by Alepha modules (e.g., i18n, router) for framework-level state.
#Default Values
Pass a default value as the second argument. It is applied only if the current value is null or undefined:
1const [prefs, setPrefs] = useStore(userPrefs, { theme: "light" });
#Non-React Access
Outside of React components, use alepha.store directly:
1// Read2const value = alepha.store.get(counter);3 4// Write5alepha.store.set(counter, { count: 42 });
Setting a value triggers the state:mutate event, which causes any useStore subscribers to re-render.
#SSR Hydration
Atoms participate in SSR automatically. On the server, state is serialized into the HTML response. On the client, the state is hydrated before React renders, so components see the server-set values on first render without flicker.
#Event System
State mutations emit a state:mutate event:
1alepha.events.on("state:mutate", ({ key, value }) => {2 console.log(`State changed: ${key}`, value);3});
This is how useStore knows when to re-render -- it listens for mutations matching its atom key.
#Example: Feature Flags
1import { $atom, t } from "alepha"; 2import { useStore } from "alepha/react"; 3 4const featureFlags = $atom({ 5 name: "app:features", 6 schema: t.object({ 7 darkMode: t.boolean(), 8 betaFeatures: t.boolean(), 9 }),10 default: {11 darkMode: false,12 betaFeatures: false,13 },14});15 16function FeatureToggle() {17 const [flags, setFlags] = useStore(featureFlags);18 19 return (20 <label>21 <input22 type="checkbox"23 checked={flags.darkMode}24 onChange={() => setFlags({ ...flags, darkMode: !flags.darkMode })}25 />26 Dark Mode27 </label>28 );29}