Skip to main content
TanStack Store provides first-class React support through the @tanstack/react-store package, which uses React’s useSyncExternalStore hook for optimal performance and automatic re-rendering.

Installation

Install the React adapter package:
npm install @tanstack/react-store
The @tanstack/react-store package re-exports everything from @tanstack/store, so you only need to install the React package.

Basic Usage

The primary way to use TanStack Store in React is through the useStore hook:
import { createStore, useStore } from '@tanstack/react-store'

// Create a store
const counterStore = createStore({
  count: 0,
})

function Counter() {
  // Subscribe to the entire store state
  const count = useStore(counterStore, (state) => state.count)

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => counterStore.setState((prev) => ({ count: prev.count + 1 }))}>
        Increment
      </button>
    </div>
  )
}

The useStore Hook

The useStore hook subscribes your component to store updates and automatically triggers re-renders when selected state changes.

Signature

function useStore<TState, TSelected>(
  store: Atom<TState> | ReadonlyAtom<TState>,
  selector: (state: TState) => TSelected,
  compare?: (a: TSelected, b: TSelected) => boolean
): TSelected

Parameters

  • store - The store instance to subscribe to
  • selector - A function that selects which part of the state you need
  • compare - (Optional) A custom equality function to determine if the selected value has changed. Defaults to Object.is

Selector Optimization

The selector function allows you to subscribe to only the parts of state you need. Your component will only re-render when the selected value changes:
import { createStore, useStore } from '@tanstack/react-store'

const appStore = createStore({
  user: { name: 'Alice', age: 30 },
  settings: { theme: 'dark', notifications: true },
})

function UserProfile() {
  // Only re-renders when user.name changes
  const userName = useStore(appStore, (state) => state.user.name)

  return <h1>Welcome, {userName}!</h1>
}

function SettingsPanel() {
  // Only re-renders when settings change
  const settings = useStore(appStore, (state) => state.settings)

  return (
    <div>
      <p>Theme: {settings.theme}</p>
      <p>Notifications: {settings.notifications ? 'On' : 'Off'}</p>
    </div>
  )
}

Custom Equality Functions

For complex state selections, you can provide a custom equality function to prevent unnecessary re-renders:
import { createStore, useStore } from '@tanstack/react-store'

const todoStore = createStore({
  todos: [
    { id: 1, text: 'Buy groceries', completed: false },
    { id: 2, text: 'Walk the dog', completed: true },
  ],
})

function deepEqual<T>(a: T, b: T): boolean {
  return JSON.stringify(a) === JSON.stringify(b)
}

function TodoList() {
  // Use deep equality to compare arrays
  const activeTodos = useStore(
    todoStore,
    (state) => state.todos.filter((todo) => !todo.completed),
    deepEqual
  )

  return (
    <ul>
      {activeTodos.map((todo) => (
        <li key={todo.id}>{todo.text}</li>
      ))}
    </ul>
  )
}

Shallow Equality

The package exports a shallow utility for shallow object comparison:
import { createStore, useStore, shallow } from '@tanstack/react-store'

const userStore = createStore({
  profile: { name: 'Alice', email: '[email protected]' },
  preferences: { theme: 'dark' },
})

function UserCard() {
  // Only re-renders if profile properties change (not the object reference)
  const profile = useStore(userStore, (state) => state.profile, shallow)

  return (
    <div>
      <h2>{profile.name}</h2>
      <p>{profile.email}</p>
    </div>
  )
}
The shallow function works with:
  • Plain objects
  • Maps
  • Sets
  • Dates
  • Symbol keys

Complete Example: Todo App

Here’s a complete example demonstrating store creation, updates, and React integration:
import { createStore, useStore } from '@tanstack/react-store'
import { useState } from 'react'

interface Todo {
  id: number
  text: string
  completed: boolean
}

interface TodoState {
  todos: Todo[]
  filter: 'all' | 'active' | 'completed'
}

const todoStore = createStore<TodoState>({
  todos: [],
  filter: 'all',
})

// Actions
const addTodo = (text: string) => {
  todoStore.setState((state) => ({
    ...state,
    todos: [
      ...state.todos,
      { id: Date.now(), text, completed: false },
    ],
  }))
}

const toggleTodo = (id: number) => {
  todoStore.setState((state) => ({
    ...state,
    todos: state.todos.map((todo) =>
      todo.id === id ? { ...todo, completed: !todo.completed } : todo
    ),
  }))
}

const setFilter = (filter: TodoState['filter']) => {
  todoStore.setState((state) => ({ ...state, filter }))
}

function TodoApp() {
  const [input, setInput] = useState('')
  const filter = useStore(todoStore, (state) => state.filter)
  const todos = useStore(todoStore, (state) => {
    const { todos, filter } = state
    if (filter === 'active') return todos.filter((t) => !t.completed)
    if (filter === 'completed') return todos.filter((t) => t.completed)
    return todos
  })

  const handleSubmit = (e: React.FormEvent) => {
    e.preventDefault()
    if (input.trim()) {
      addTodo(input)
      setInput('')
    }
  }

  return (
    <div>
      <h1>Todo App</h1>
      
      <form onSubmit={handleSubmit}>
        <input
          value={input}
          onChange={(e) => setInput(e.target.value)}
          placeholder="What needs to be done?"
        />
        <button type="submit">Add</button>
      </form>

      <div>
        <button onClick={() => setFilter('all')}>All</button>
        <button onClick={() => setFilter('active')}>Active</button>
        <button onClick={() => setFilter('completed')}>Completed</button>
      </div>

      <ul>
        {todos.map((todo) => (
          <li key={todo.id}>
            <input
              type="checkbox"
              checked={todo.completed}
              onChange={() => toggleTodo(todo.id)}
            />
            <span style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}>
              {todo.text}
            </span>
          </li>
        ))}
      </ul>
    </div>
  )
}

export default TodoApp

React-Specific Considerations

Concurrent Features

The React adapter uses useSyncExternalStore under the hood, which means it’s fully compatible with React 18’s concurrent features including:
  • Concurrent rendering
  • Automatic batching
  • Transitions
  • Suspense (when combined with proper error boundaries)

Server-Side Rendering (SSR)

TanStack Store works seamlessly with SSR. Create stores outside of components and initialize them with your server-side data:
import { createStore } from '@tanstack/react-store'

// Create the store at module level
const appStore = createStore({
  data: null,
  isLoading: false,
})

// Initialize with SSR data
export function initializeStore(initialData: any) {
  appStore.setState({ data: initialData, isLoading: false })
}

Performance Tips

  1. Use selectors wisely: Select only what you need to minimize re-renders
  2. Leverage equality functions: Use shallow or custom comparisons for complex objects
  3. Memoize selectors: For expensive computations, consider memoizing your selector functions
  4. Create multiple stores: Instead of one large store, create multiple smaller stores for different domains

Derived Stores

You can create derived stores that automatically update based on other stores:
import { createStore, useStore } from '@tanstack/react-store'

const countStore = createStore(0)
const doubleStore = createStore(() => ({ value: countStore.state * 2 }))

function CounterDisplay() {
  const count = useStore(countStore, (state) => state)
  const double = useStore(doubleStore, (state) => state.value)

  return (
    <div>
      <p>Count: {count}</p>
      <p>Double: {double}</p>
      <button onClick={() => countStore.setState((prev) => prev + 1)}>
        Increment
      </button>
    </div>
  )
}

API Reference

For detailed API documentation, see:
  • useStore - React hook for subscribing to stores
  • shallow - Shallow equality comparison utility
  • createStore - Core store creation API

TypeScript Support

The React adapter provides full TypeScript support with automatic type inference:
import { createStore, useStore } from '@tanstack/react-store'

interface AppState {
  user: { name: string; id: number }
  isAuthenticated: boolean
}

const store = createStore<AppState>({
  user: { name: '', id: 0 },
  isAuthenticated: false,
})

function Component() {
  // TypeScript knows the exact shape of state
  const userName = useStore(store, (state) => state.user.name)
  // userName is typed as string
  
  return <div>{userName}</div>
}