Skip to main content
Stan.js is designed with React in mind, providing seamless integration through hooks and automatic re-render optimization.

Basic Usage

Create a store and use the useStore hook to access state and actions:
import { createStore } from 'stan-js'

export const { useStore, actions, getState } = createStore({
  counter: 0,
  message: 'Hello, Stan!',
  users: [] as Array<string>
})

Using the Hook

The useStore hook returns both state values and auto-generated setter functions:
import { useStore } from './store'

const Counter = () => {
  const { counter, setCounter } = useStore()

  return (
    <section>
      <button onClick={() => setCounter(prev => prev - 1)}>-</button>
      <span>{counter}</span>
      <button onClick={() => setCounter(prev => prev + 1)}>+</button>
    </section>
  )
}
Each state property automatically gets a setter function named set{PropertyName} (camelCase). For example, counter gets setCounter, userName gets setUserName.

Optimized Re-renders

Stan.js uses a proxy-based subscription system that only subscribes to the state values you actually use:
const MessageInput = () => {
  // Only subscribes to 'message', not 'counter' or 'users'
  const { setMessage } = useStore()

  return (
    <input
      onChange={event => setMessage(event.target.value)}
    />
  )
}
If a component only uses setter functions without accessing state values, it won’t re-render when state changes. This is a powerful optimization for action-only components.

Accessing State Without Subscriptions

Use getState() to read current values without triggering re-renders:
import { getState } from './store'

const MessageInput = () => {
  const { setMessage } = useStore()

  return (
    <input
      defaultValue={getState().message}
      onChange={event => setMessage(event.target.value)}
    />
  )
}

Computed Values

Define computed properties using getters for derived state:
export const { useStore } = createStore({
  message: 'hello world',
  get upperCaseMessage() {
    return this.message.toUpperCase()
  }
})
const Message = () => {
  const { upperCaseMessage } = useStore()
  return <span>Uppercased: <b>{upperCaseMessage}</b></span>
}
Computed values automatically update when their dependencies change. Stan.js tracks which properties are accessed in the getter.

Custom Actions

Create custom actions that perform multiple updates atomically:
export const { useStore } = createStore(
  {
    firstName: 'John',
    lastName: 'Doe'
  },
  ({ actions }) => ({
    setUser: (firstName: string, lastName: string) => {
      actions.setFirstName(firstName)
      actions.setLastName(lastName)
    }
  })
)
Custom actions are automatically batched to prevent multiple re-renders:
const UserForm = () => {
  const { setUser } = useStore()

  const handleSubmit = () => {
    // Only triggers ONE re-render, not two
    setUser('Jane', 'Smith')
  }

  return <button onClick={handleSubmit}>Update User</button>
}

Effects and Side Effects

Use useStoreEffect to react to state changes:
import { useStoreEffect } from './store'

const UserLogger = () => {
  useStoreEffect(state => {
    console.log('State changed:', state.counter)
  })

  return null
}
The effect automatically tracks which state values you access and only runs when those values change:
useStoreEffect(({ counter }) => {
  // Only runs when 'counter' changes, not 'message' or 'users'
  console.log('Counter changed:', counter)
})

Dependency Array

Add a dependency array to trigger effects on external changes:
const UserSync = ({ userId }) => {
  useStoreEffect(state => {
    syncUserData(userId, state)
  }, [userId])

  return null
}

Resetting State

Reset specific values or the entire store to initial state:
import { reset } from './store'

const ResetButton = () => {
  return (
    <>
      <button onClick={() => reset('counter')}>Reset Counter</button>
      <button onClick={() => reset()}>Reset All</button>
    </>
  )
}

Updates Outside React

Use actions directly for updates outside React components:
import { actions } from './store'

// Update state from anywhere
setInterval(() => {
  actions.setCurrentTime(new Date())
}, 1000)

// Async updates work seamlessly
const fetchUsers = async () => {
  const response = await fetch('/api/users')
  const data = await response.json()
  actions.setUsers(data)
}
While you can update state from anywhere, be cautious with updates during render. Stan.js handles this correctly, but it’s generally better to use effects or event handlers.

Batch Updates

Manually batch multiple updates to trigger a single re-render:
import { batchUpdates, actions } from './store'

const handleBulkUpdate = () => {
  batchUpdates(() => {
    actions.setCounter(0)
    actions.setMessage('Reset')
    actions.setUsers([])
  })
  // Only ONE re-render happens after all updates
}

Best Practices

Split Components

Create small components that only access the state they need. This minimizes re-renders.

Use getState Wisely

Use getState() for one-time reads that don’t need to trigger re-renders.

Leverage Computed Values

Use getters for derived state instead of calculating in components.

Custom Actions

Group related updates in custom actions for better encapsulation and automatic batching.

Type Safety

Stan.js provides full TypeScript support with automatic type inference:
const { counter, setCounter } = useStore()
//      ^? number
//                 ^? (value: number | ((prev: number) => number)) => void
See the TypeScript guide for advanced type patterns.

Build docs developers (and LLMs) love