Skip to main content

Overview

The useStore hook provides access to store state and actions within React components. It automatically subscribes to only the state properties that are accessed, optimizing re-renders.

Signature

function useStore(): TState & Actions<TState> & TCustomActions

Parameters

None. The hook is created by createStore and bound to that specific store instance.

Return Value

Returns an object containing:
state properties
TState
All state properties from the store. Accessing a property automatically subscribes the component to changes for that property only.
actions
Actions<TState>
All setter actions following the set[PropertyName] pattern. Each action accepts either a new value or an updater function.
custom actions
TCustomActions
Any custom actions defined in the customActionsBuilder when creating the store.

Behavior

  • Automatic Subscriptions: The component only re-renders when accessed state properties change
  • Proxy-based: Uses a Proxy to track which properties are accessed during render
  • Action Stability: Action functions are stable and don’t cause re-renders when used
  • Selective Updates: Accessing only actions (not state) means the component never re-renders from state changes

Examples

Basic Usage

import { createStore } from 'stan-js'

const counterStore = createStore({
  count: 0,
  name: 'Counter'
})

function Counter() {
  const { count, setCount } = counterStore.useStore()
  
  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(prev => prev + 1)}>
        Increment
      </button>
    </div>
  )
}

Selective Subscriptions

const store = createStore({
  user: 'John',
  theme: 'light',
  count: 0
})

// Only subscribes to 'user'
function UserDisplay() {
  const { user } = store.useStore()
  return <div>User: {user}</div>
}

// Only subscribes to 'theme'
function ThemeToggle() {
  const { theme, setTheme } = store.useStore()
  return (
    <button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
      Theme: {theme}
    </button>
  )
}

// Changes to 'count' won't re-render either component above
store.actions.setCount(5)

Actions Only (No Re-renders)

const store = createStore({
  count: 0
})

// This component never re-renders from state changes
// because it only accesses actions, not state
function ResetButton() {
  const { setCount } = store.useStore()
  
  return (
    <button onClick={() => setCount(0)}>
      Reset
    </button>
  )
}

With Computed Properties

const store = createStore({
  firstName: 'John',
  lastName: 'Doe',
  get fullName() {
    return `${this.firstName} ${this.lastName}`
  }
})

function UserProfile() {
  const { fullName, setFirstName, setLastName } = store.useStore()
  
  return (
    <div>
      <h1>{fullName}</h1>
      <input 
        placeholder="First name"
        onChange={(e) => setFirstName(e.target.value)} 
      />
      <input 
        placeholder="Last name"
        onChange={(e) => setLastName(e.target.value)} 
      />
    </div>
  )
}

With Custom Actions

const cartStore = createStore(
  {
    items: [],
    total: 0
  },
  ({ actions, getState }) => ({
    addItem: (item: CartItem) => {
      const state = getState()
      actions.setItems([...state.items, item])
      actions.setTotal(state.total + item.price)
    },
    clearCart: () => {
      actions.setItems([])
      actions.setTotal(0)
    }
  })
)

function ShoppingCart() {
  const { items, total, addItem, clearCart } = cartStore.useStore()
  
  return (
    <div>
      <ul>
        {items.map(item => (
          <li key={item.id}>{item.name} - ${item.price}</li>
        ))}
      </ul>
      <p>Total: ${total}</p>
      <button onClick={clearCart}>Clear Cart</button>
    </div>
  )
}

With Storage Persistence

import { storage } from 'stan-js'

const settingsStore = createStore({
  theme: storage('light'),
  language: storage('en')
})

function Settings() {
  const { theme, language, setTheme, setLanguage } = settingsStore.useStore()
  
  return (
    <div>
      <select value={theme} onChange={(e) => setTheme(e.target.value)}>
        <option value="light">Light</option>
        <option value="dark">Dark</option>
      </select>
      <select value={language} onChange={(e) => setLanguage(e.target.value)}>
        <option value="en">English</option>
        <option value="es">Spanish</option>
      </select>
    </div>
  )
}

Multiple Stores

const userStore = createStore({ name: 'John', email: '[email protected]' })
const settingsStore = createStore({ theme: 'light', notifications: true })

function Profile() {
  const { name, email } = userStore.useStore()
  const { theme } = settingsStore.useStore()
  
  return (
    <div className={theme}>
      <h1>{name}</h1>
      <p>{email}</p>
    </div>
  )
}

Performance Tips

Only destructure the properties you need. Each destructured state property creates a subscription.
// Good: Only subscribes to 'count'
const { count, setCount } = store.useStore()

// Less optimal: Subscribes to all properties
const state = store.useStore()
If you only need actions, only destructure actions to avoid unnecessary re-renders.
// This component never re-renders from state changes
const { setCount } = store.useStore()

Type Safety

The hook is fully type-safe and infers the correct types from your store definition:
const store = createStore({
  count: 0,
  user: { name: 'John', age: 30 }
})

function Component() {
  const { count, user, setCount, setUser } = store.useStore()
  
  // TypeScript knows:
  // count: number
  // user: { name: string, age: number }
  // setCount: (value: number | ((prev: number) => number)) => void
  // setUser: (value: { name: string, age: number } | ((prev: ...) => ...)) => void
}

Comparison with useSyncExternalStore

Under the hood, useStore uses React’s useSyncExternalStore but provides several enhancements:
  • Automatic selective subscriptions (proxy-based)
  • Integrated actions in the return value
  • Optimized shallow equality checks
  • Support for computed properties

Notes

  • The hook must be called at the top level of your component (standard React rules)
  • Accessing a property in conditional logic will still create a subscription
  • The same store can be used in multiple components simultaneously
  • State updates are synchronous and immediately reflected
  • Computed properties automatically track their dependencies

Build docs developers (and LLMs) love