React
useStore($cart)useStore($cart)useStore($cart)useStore($cart)$cart — Svelte’s store contractuseStore($cart)new StoreController(this, $cart)nanostores.useStore($cart)x-nano:cart="$cart"$cart.subscribe(render)Persistence, data fetching, routing, translations, media queries, and nested state.
Swap atom for persistentJSON. The value is saved to localStorage, and other open tabs get every change.
export const $cart = persistentJSON<Product[]>('cart', [])On the server there is no localStorage: the store starts from its initial value.
Used in: Mostly HTML, a few parts share state →A fetcher store loads on the first subscriber, caches by key and shares the result. Every widget just reads $products.
export const [createFetcherStore] = nanoquery({
fetcher: (...keys) => fetch(keys.join('')).then((r) => r.json()),
})
export const $products = createFetcherStore<Product[]>(['/api/products'])Already on TanStack Query? Keep it for server data and use stores for client state.
The router is a store of the current page. Read the query string as one more computed store.
export const $router = createRouter({
catalog: '/catalog',
product: '/products/:id',
})
// /catalog?color=red → { color: 'red' }
export const $filters = computed($router, (page) => page?.search ?? {})Components declare their strings in the base language. Translations for the current locale load on demand.
export const $locale = localeFrom(browser({ available: ['en', 'de'] }))
export const i18n = createI18n($locale, {
get: (code) => fetch(`/i18n/${code}.json`).then((r) => r.json()),
})
export const $cartMessages = i18n('cart', {
title: 'Your cart',
})No plugin needed. onMount starts listening when the first component subscribes and stops after the last one leaves.
export const $isMobile = atom(false)
onMount($isMobile, () => {
const query = matchMedia('(max-width: 767px)')
const update = () => $isMobile.set(query.matches)
update()
query.addEventListener('change', update)
return () => query.removeEventListener('change', update)
})Update a field by its path instead of copying the whole object by hand.
export const $checkout = deepMap({
name: '',
address: { city: '', zip: '' },
})
export function setCity(city: string) {
$checkout.setKey('address.city', city)
}Flat object? map in the core has setKey too.
More packages
Your stores in the Redux DevTools panel, named for you. Or a plain console log when that is enough.
Every store appears in the Redux DevTools panel, named after the variable you wrote. Each change gets its own timeline row with a diff and a stack trace to the line that wrote it.
export default defineConfig({
plugins: [nanostoresDevtools()],
})connectDevtools({ name: 'my-shop' })No browser extension: store changes and lifecycle events appear in the console under the names you pass.
logger({ Cart: $cart })The Vue integration adds your stores to the Vue Devtools panel.
nanostores-devtools only reads state, so there is no time travel. Built your workflow around it? Redux Toolkit is the stronger fit.