Framework adapters

One state for several frameworks →

Store utilities

Persistence, data fetching, routing, translations, media queries, and nested state.

Data fetching

A fetcher store loads on the first subscriber, caches by key and shares the result. Every widget just reads $products.

stores/products.ts
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.

Routing

The router is a store of the current page. Read the query string as one more computed store.

@nanostores/router →officialOnly the query string: @vp-tw/nanostores-qs ↗ community
stores/router.ts
export const $router = createRouter({
  catalog: '/catalog',
  product: '/products/:id',
})

// /catalog?color=red → { color: 'red' }
export const $filters = computed($router, (page) => page?.search ?? {})

Translations

Components declare their strings in the base language. Translations for the current locale load on demand.

stores/i18n.ts
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',
})

Media queries

No plugin needed. onMount starts listening when the first component subscribes and stops after the last one leaves.

onMount →coreReady-made store: @nanostores/media-query ↗ official
stores/layout.ts
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)
})

Nested state

Update a field by its path instead of copying the whole object by hand.

stores/checkout.ts
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

Missing a package? Tell us →

Devtools

Your stores in the Redux DevTools panel, named for you. Or a plain console log when that is enough.

Print every change to the console

No browser extension: store changes and lifecycle events appear in the console under the names you pass.

dev.ts
logger({ Cart: $cart })

Already working in Vue Devtools?

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.

Getting started →Your first store and component in a few minutes.Find your situation →Islands, Context, live data, several frameworks.