Nano Stores is a state management library designed for small, fast, and easily tree-shakeable applications. It focuses on moving application logic into stores, making the codebase more modular and testable. It supports multiple frameworks (React, Preact, Vue, Svelte, Solid, Lit, Angular, and vanilla JS).
Nano Stores Persistent (for local storage synchronization): This library extends Nano Stores to manage state persistently in localStorage
and synchronize changes across browser tabs.
Core Concepts:
- Atoms: Basic store for primitive types (e.g., numbers, strings, arrays, objects).
- Maps: Stores key-value pairs. Changes to keys are tracked and can be used to trigger updates. Handles updating (like
setKey()
) instead of complete replacement. - Deep Maps: Stores objects with nested objects and arrays, maintaining fine-grained reactivity throughout the structure.
- Computed Stores: Stores calculated from other store values. Evaluates the dependent stores on update without redundant logic. Use
computed
(standard update on change) orbatched
(update only once per frame). - Tasks: Allows you to wrap asynchronous operations, so subsequent tasks are blocked until the current ones complete. Handles tasks effectively during SSR.
- Store Lifecycle: Stores have "mount" and "disable" states, ensuring resources are used only when necessary. This manages things like data loading or establishing network connections.
onMount()
andonStop()
are used to manage this. - Value Encoding: Allows pre-processing of data when storing values in persistent stores using custom
encode
,decode
functions.
Common Functions (Illustrative):
Atoms (atom
)
import { atom } from 'nanostores'
export const $counter = atom(0)
$counter.set(value)
: Sets the value of the counter. Example:$counter.set($counter.get() + 1)
.$counter.get()
: Returns the current value. Example:const currentCount = $counter.get()
.$counter.subscribe(callback)
: Subscribes to changes in the store. Example:$counter.subscribe(value => console.log(value))
.$counter.listen(callback)
: listers are called only when the store changes. Example:$counter.listen(value => console.log(value))
.
Maps (map
)
import { map } from 'nanostores'
export const $profile = map({ name: 'anonymous' })
$profile.set(object)
: Sets the entire object at once. If you have$profile.setKey()
then it should be used.$profile.setKey(key, newValue)
: Sets a specific key's value. Example:$profile.setKey('name', 'Updated Name')
. Useful for dynamic object updates.$profile.listen((profile, previousProfile, changedKey) => { /* ... */ })
: Listens for both full object and specific updates. Receives the current & old objects and the updated key.
Persistent Stores (Example):
import { persistentAtom } from '@nanostores/persistent'
export const shoppingCart = persistentAtom<Product[]>('cart', [], {
encode: JSON.stringify,
decode: JSON.parse,
})
- Save and sync data in
localStorage
. - Use
encode
/decode
options to support complex value-types like objects or arrays.
Server-Side Rendering (Illustrative):
if (isServer) {
locale.set(user.locale) // Initialize stores in the server context
}
- Stores will not use
localStorage
in server-side environment, but they can be initialized.
Framework Integration (Illustrative):
import { useStore } from '@nanostores/react' // React integration
import { $profile } from './profile.js'
export const Header = () => {
const profile = useStore($profile)
return <header>Hi, {profile.name}</header>
}
Other Notes:
listenKeys()
allows for fine-grained control over which keys trigger updates in map stores.onMount
,onStart
,onStop
,onSet
, andonNotify
can be used to manage store lifecycle.onMount
is generally preferred for handling the lazy load behavior of stores.- Testing techniques like
useTestStorageEngine
are provided to work with mock storage engines. cleanStores
andkeepMount
support lazy stores in testing, ensuring clean tests.