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

— Here's your state manager.— But it's 351 bytes!!
Sano di Pietro, 1444 · Louvre, MI 472
1

Don't like app logic in React hooks?

The business rule lives in a component.Its unit test:
Toulouse, BM, MS 815 · f. 60r
useStateuseEffectuseMemo
Before · logic in hooks
export 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>
  )
}
After · logic in a store

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.
2

The second Astro island needs the same cart.

Our islands are completely independent.
Morgan Library, MS M.638 · f. 36v
3

One state for React, Vue and Svelte users.

your-lib/session.ts · core
export const $session = atom<Session>(null)
export const $signedIn = computed($session, (session) => !!session)
export async function signOut() {
  await api.signOut()
  $session.set(null)
}
React users
import { useStore } from '@nanostores/react'
export function Account() {
  const session = useStore($session)
  return <span>{session?.name ?? 'Guest'}</span>
}
Vue users
<script setup lang="ts">
import { useStore } from '@nanostores/vue'
const session = useStore($session)
</script>

<template>
  <span>{{ session?.name ?? 'Guest' }}</span>
</template>
Svelte users · no adapter
<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>
Vanilla JS users · no framework
$session.subscribe((session) => {
  for (const el of document.querySelectorAll('[data-user-name]')) {
    el.textContent = session?.name ?? 'Guest'
  }
})
document.querySelector('#sign-out')?.addEventListener('click', signOut)
— We need the cart logic in Vue.— Nah, I'm a React hook till the end of the project.
British Library, Cotton MS Nero D IX · f. 40
4

React Context keeps growing. Redux is on the table.

Today · providers
export function App() {
  return (
    <CartProvider>
      <UserProvider>
        <FiltersProvider>
          <main>App</main>
        </FiltersProvider>
      </UserProvider>
    </CartProvider>
  )
}
With a store · no provider
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
— Context is shared.— Certainly.— Well, tell me more, please.
F. Herlin, 1466 · St. Jakob, Rothenburg

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 →
— Got a router, man?— No.— Here's a router, man.
Besançon, BM, Ms 69 · p. 269

We think logic belongs in stores, not components.

UI as a skin: components render, stores decide what is true.

logic in componentsuseEffectfetch()deps [ ]componentorlogic in storesstoredata · loading · rulesrenderscomponentsUI as a skin

Getting started →

Install, Create a store, Derive a value, Read it in a component