Every React developer has been there: Redux boilerplate hell, Context prop drilling nightmares, or Zustand stores that don't survive SSR.
Alepha has a simple state system built around two primitives: $atom for defining state, and $use for consuming it.
In Next.js or Remix, sharing state between server and client is painful:
1// next.js way - manual serialization, hydration mismatches2export async function getServerSideProps() {3 const user = await getUser();4 return { props: { user } }; // hope it serializes correctly5}
In Redux, you need a store, reducers, actions, and a hydration strategy. That's four files minimum for "I want to share some data."
An atom is a piece of state with a schema, a name, and optionally a default value.
1import { $atom } from "alepha"; 2import { t } from "alepha"; 3 4const userPreferences = $atom({ 5 name: "user.preferences", 6 schema: t.object({ 7 theme: t.enum(["light", "dark"]), 8 language: t.text(), 9 notifications: t.boolean(),10 }),11 default: {12 theme: "light",13 language: "en",14 notifications: true,15 },16});
That's it. No store. No provider. No boilerplate.
$useIn your services, use $use to get the current value:
1class ThemeService {2 // reactive reference to the atom3 prefs = $use(userPreferences);4 5 isDarkMode() {6 return this.prefs.theme === "dark";7 }8}
In React components, the value updates automatically:
1const ThemeToggle = () => {2 const prefs = $use(userPreferences);3 4 return (5 <button onClick={() => prefs.theme = prefs.theme === "dark" ? "light" : "dark"}>6 Current: {prefs.theme}7 </button>8 );9};
Yes, you can mutate directly. No action dispatching. No reducers.
Here's where Alepha shines. Atoms automatically serialize on the server and hydrate on the client.
1// server: atom value is serialized into HTML2// client: atom hydrates with server value, no flash of wrong content3 4const Dashboard = () => {5 const prefs = $use(userPreferences);6 7 // no hydration mismatch, no loading state8 return <div className={prefs.theme}>...</div>;9};
Compare this to Next.js where you'd need useEffect to avoid hydration errors, or Redux where you manually rehydrate the store.
You can set atom values from anywhere:
1// direct mutation (in components or services)2prefs.theme = "dark";3 4// or get the alepha instance and use setState5alepha.state(userPreferences, { theme: "dark" });
Atoms can persist to localStorage, cookies, or even Redis:
1const sessionData = $atom({2 name: "session",3 schema: t.object({4 token: t.optional(t.text()),5 expiresAt: t.optional(t.number()),6 }),7 persist: "localStorage", // survives page refresh8});
Options: "localStorage", "sessionStorage", "cookie", or a custom adapter.
| Scenario | Use |
|---|---|
| User preferences, theme, language | $atom |
| Authentication state | $atom |
Page-specific data from resolve |
Props via $page |
| Form state | useForm hook |
| Temporary UI state (modal open) | useState |
Atoms are for global state that multiple components need. Don't atom-ify everything.
Redux:
1// store.ts, userSlice.ts, actions.ts, selectors.ts...2// 100+ lines for user preferences3const dispatch = useDispatch();4dispatch(setTheme("dark"));
Jotai:
1// simpler, but no SSR hydration story2const themeAtom = atom("light");3const [theme, setTheme] = useAtom(themeAtom);
Alepha:
1// type-safe, SSR-ready, one file2const prefs = $use(userPreferences);3prefs.theme = "dark";
Need derived state? Just compute it:
1class AppState {2 prefs = $use(userPreferences);3 4 // computed on access, no memoization needed5 get cssClass() {6 return this.prefs.theme === "dark" ? "dark-mode" : "light-mode";7 }8}
Atoms show up in Alepha DevTools (/devtools). You can inspect current values, see which components are subscribed, and even modify state live.
No Redux DevTools extension needed. No setup. It's built in.
State management doesn't have to be complicated. Define your shape with $atom, read it with $use, and let Alepha handle the rest.