A tiny state manager with atomic, tree-shakable stores.
- 351–844 bytes
- minified and brotlied
- 0
- dependencies
- Hook-free
- core API
- 11.8M
- downloads a week. Used by Clerk and Better Auth, recommended by Astro
React, Vue, Svelte, Solid, Lit, Angular, vanilla JS and more
Don't like app logic in React hooks?
useStateuseEffectuseMemoexport function Header() {
const [user, setUser] =
useState<User | null>(null)
useEffect(() => {
fetchUser().then(setUser)
}, [])
const isAdmin = useMemo(
() => user?.roles.includes('admin'),
[user],
)
return (
<span>
{isAdmin ? 'Admin' : 'User'}
</span>
)
}
const $user = atom<User | null>(null)
onMount($user, () => {
fetchUser().then($user.set)
})
const $isAdmin = computed($user, (user) =>
user?.roles.includes('admin'),
)
export function Header() {
const isAdmin = useStore($isAdmin)
return (
<span>
{isAdmin ? 'Admin' : 'User'}
</span>
)
}Like your logic in hooks? Zustand is a great choice.
The second Astro island needs the same cart.
One state for React, Vue and Svelte users.
export const $session = atom<Session>(null)
export const $signedIn = computed($session, (session) => !!session)
export async function signOut() {
await api.signOut()
$session.set(null)
}import { useStore } from '@nanostores/react'
export function Account() {
const session = useStore($session)
return <span>{session?.name ?? 'Guest'}</span>
}<script setup lang="ts">
import { useStore } from '@nanostores/vue'
const session = useStore($session)
</script>
<template>
<span>{{ session?.name ?? 'Guest' }}</span>
</template><script lang="ts">
// `$` is reserved here, so the store is renamed on import.
import { $session as session } from './session'
</script>
<span>{$session?.name ?? 'Guest'}</span>$session.subscribe((session) => {
for (const el of document.querySelectorAll('[data-user-name]')) {
el.textContent = session?.name ?? 'Guest'
}
})
document.querySelector('#sign-out')?.addEventListener('click', signOut)React Context keeps growing. Redux is on the table.
export function App() {
return (
<CartProvider>
<UserProvider>
<FiltersProvider>
<main>App</main>
</FiltersProvider>
</UserProvider>
</CartProvider>
)
}export const $cart = atom<Product[]>([])
export const addItem = (item: Product) => $cart.set([...$cart.get(), item])
export function CartCount() {
const cart = useStore($cart)
return <span>{cart.length}</span>
}Theme, localeKeep Context
Time travel, strict patternsRedux Toolkit
Shared state, less wiringA small store
Small library. Big ecosystem
Small core, by design. Here's what's around it.
- What if we need more than the core?
- Persistence, data fetching, routing, i18n: separate official packages. Each one is a store you add only when you need it. Ecosystem →
- Can we see what the stores are doing?
- Every store and change in Redux DevTools, with a stack trace to the line that wrote it. No time travel. Devtools →
- Does it work with SSR?
- On the client, yes. On the server a store is shared by all requests, so keep per-request data out of it. Astro recipe →
- Is it still moving?
- Changelog →
We think logic belongs in stores, not components.
UI as a skin: components render, stores decide what is true.
Getting started →
Install, Create a store, Derive a value, Read it in a component