Skip to main content
TanStack Store provides Preact support through the @tanstack/preact-store package, which implements a lightweight version of React’s useSyncExternalStore for optimal performance.

Installation

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

Basic Usage

The primary way to use TanStack Store in Preact is through the useStore hook:
import { createStore, useStore } from '@tanstack/preact-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,
  options?: { equal?: (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
  • options.equal - (Optional) A custom equality function. Defaults to shallow

Implementation Details

The Preact adapter includes a custom implementation of useSyncExternalStore that:
  • Avoids importing preact/compat (no side effects)
  • Uses useLayoutEffect and useEffect for optimal timing
  • Provides the same guarantees as React’s useSyncExternalStore

Selector Optimization

The selector function allows you to subscribe to only the parts of state you need:
import { createStore, useStore } from '@tanstack/preact-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:
import { createStore, useStore } from '@tanstack/preact-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),
    { equal: 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 (this is the default):
import { createStore, useStore, shallow } from '@tanstack/preact-store'

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

function UserCard() {
  // Uses shallow comparison by default
  const profile = useStore(userStore, (state) => state.profile, { equal: 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 Preact integration:
import { h } from 'preact'
import { useState } from 'preact/hooks'
import { createStore, useStore } from '@tanstack/preact-store'

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: Event) => {
    e.preventDefault()
    if (input.trim()) {
      addTodo(input)
      setInput('')
    }
  }

  return (
    <div>
      <h1>Todo App</h1>
      
      <form onSubmit={handleSubmit}>
        <input
          value={input}
          onInput={(e) => setInput((e.target as HTMLInputElement).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

Preact-Specific Considerations

No preact/compat Required

Unlike some libraries, @tanstack/preact-store doesn’t require preact/compat. It includes a custom implementation of useSyncExternalStore specifically for Preact.

Lightweight Implementation

The adapter is highly optimized:
  • Small bundle size
  • No external dependencies (except Preact itself)
  • Efficient re-render behavior

Server-Side Rendering (SSR)

TanStack Store works with Preact SSR. Create stores outside of components and initialize them with server data:
import { createStore } from '@tanstack/preact-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 shallow equality: The default works well for most cases
  3. Memoize selectors: For expensive computations, consider memoizing selector functions
  4. Create multiple stores: Instead of one large store, create multiple smaller stores

Derived Stores

You can create derived stores that automatically update based on other stores:
import { createStore, useStore } from '@tanstack/preact-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>
  )
}

Migration from React

If you’re migrating from React, the API is identical:
// React
import { createStore, useStore } from '@tanstack/react-store'

// Preact - same API!
import { createStore, useStore } from '@tanstack/preact-store'
The only difference is the import path. Your component code can remain the same.

Comparison with Preact Signals

Preact has built-in signals. Here’s when to use TanStack Store: Use TanStack Store when:
  • You want a consistent API across multiple frameworks
  • You need advanced features like derived stores
  • You’re migrating from React
  • You want structured state management
Use Preact Signals when:
  • You want the lightest possible solution
  • You’re building a Preact-only app
  • You prefer a more reactive programming style
You can also use both together!

API Reference

For detailed API documentation, see:
  • See the API reference for detailed documentation:

TypeScript Support

The Preact adapter provides full TypeScript support with automatic type inference:
import { createStore, useStore } from '@tanstack/preact-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>
}