What is a Store?
A Store is TanStack Store’s primary state management primitive. It provides a simple, type-safe way to manage mutable, reactive state in your application. Stores can be created from initial values or getter functions, and they automatically notify subscribers when their state changes.
Creating Stores
There are two ways to create a store using the createStore function:
From an Initial Value
Create a mutable store by passing an initial value:
import { createStore } from '@tanstack/store'
const countStore = createStore ( 0 )
// Update the state
countStore . setState (( prev ) => prev + 1 )
// Read the current state
console . log ( countStore . state ) // 1
From a Getter Function (Read-only)
Create a read-only store by passing a getter function:
import { createStore } from '@tanstack/store'
const doubleStore = createStore (() => countStore . state * 2 )
// This is read-only, no setState method
console . log ( doubleStore . state ) // 2
When you pass a function to createStore, it returns a ReadonlyStore<T> that cannot be updated directly. This is useful for derived state.
Store API
Type Signatures
class Store < T > {
constructor ( initialValue : T )
setState ( updater : ( prev : T ) => T ) : void
get state () : T
get () : T
subscribe (
observerOrFn : Observer < T > | (( value : T ) => void )
) : Subscription
}
class ReadonlyStore < T > {
constructor ( getValue : ( prev ?: NoInfer < T >) => T )
get state () : T
get () : T
subscribe (
observerOrFn : Observer < T > | (( value : T ) => void )
) : Subscription
}
// Factory function with overloads
function createStore < T >( initialValue : T ) : Store < T >
function createStore < T >(
getValue : ( prev ?: NoInfer < T >) => T
) : ReadonlyStore < T >
Methods
setState(updater)
Updates the store’s state using an updater function. Only available on mutable stores (not read-only stores).
const store = createStore ({ count: 0 , name: 'Alice' })
store . setState (( prev ) => ({
... prev ,
count: prev . count + 1 ,
}))
setState only exists on Store<T>, not ReadonlyStore<T>. Read-only stores are automatically recomputed when their dependencies change.
state / get()
Both properties return the current state value. They are functionally equivalent:
const store = createStore ( 42 )
console . log ( store . state ) // 42
console . log ( store . get ()) // 42
subscribe(observerOrFn)
Subscribes to changes in the store. Returns a subscription object with an unsubscribe method.
const store = createStore ( 0 )
const subscription = store . subscribe (( value ) => {
console . log ( 'Store updated:' , value )
})
// Later, unsubscribe
subscription . unsubscribe ()
You can also pass an observer object:
const subscription = store . subscribe ({
next : ( value ) => console . log ( 'Next:' , value ),
error : ( err ) => console . error ( 'Error:' , err ),
complete : () => console . log ( 'Complete' ),
})
Practical Examples
Counter Store
import { createStore } from '@tanstack/store'
const counterStore = createStore ( 0 )
// Subscribe to changes
counterStore . subscribe (( count ) => {
console . log ( 'Count:' , count )
})
// Increment
counterStore . setState (( prev ) => prev + 1 ) // Logs: "Count: 1"
// Decrement
counterStore . setState (( prev ) => prev - 1 ) // Logs: "Count: 0"
Todo List Store
interface Todo {
id : number
text : string
completed : boolean
}
const todosStore = createStore < Todo []>([])
function addTodo ( text : string ) {
todosStore . setState (( prev ) => [
... prev ,
{ id: Date . now (), text , completed: false },
])
}
function toggleTodo ( id : number ) {
todosStore . setState (( prev ) =>
prev . map (( todo ) =>
todo . id === id ? { ... todo , completed: ! todo . completed } : todo
)
)
}
function deleteTodo ( id : number ) {
todosStore . setState (( prev ) => prev . filter (( todo ) => todo . id !== id ))
}
User Settings Store
interface UserSettings {
theme : 'light' | 'dark'
language : string
notifications : boolean
}
const settingsStore = createStore < UserSettings >({
theme: 'light' ,
language: 'en' ,
notifications: true ,
})
// Update a single setting
function setTheme ( theme : 'light' | 'dark' ) {
settingsStore . setState (( prev ) => ({ ... prev , theme }))
}
// Subscribe to theme changes only
settingsStore . subscribe (( settings ) => {
document . documentElement . setAttribute ( 'data-theme' , settings . theme )
})
How Stores Work Internally
Stores are a lightweight wrapper around atoms . When you create a store:
An atom is created internally using createAtom
The store provides a simpler API surface with state, setState, and subscribe
For mutable stores, setState calls the atom’s set method
For read-only stores, the store automatically recomputes when dependencies change
From store.ts:4-29:
export class Store < T > {
private atom : Atom < T >
constructor ( valueOrFn : T | (( prev ?: T ) => T )) {
this . atom = createAtom (
valueOrFn as T | (( prev ?: NoInfer < T >) => T ),
) as Atom < T >
}
public setState ( updater : ( prev : T ) => T ) {
this . atom . set ( updater )
}
public get state () {
return this . atom . get ()
}
public subscribe (
observerOrFn : Observer < T > | (( value : T ) => void ),
) : Subscription {
return this . atom . subscribe ( toObserver ( observerOrFn ))
}
}
When to Use Stores
Use Stores When
You need simple, mutable state
You want a high-level API
You’re building application state
You want built-in reactivity
Use Atoms When
You need fine-grained control
You’re building a library
You need custom comparison logic
You want lower-level primitives
Atoms Lower-level reactive primitives that power stores
Derived Stores Create stores that automatically compute from other stores
Subscriptions Learn how to react to state changes
Batching Optimize performance by batching multiple updates