Skip to main content

Overview

This guide helps you migrate between major versions of TanStack Store and understand breaking changes. TanStack Store follows semantic versioning, so major version bumps indicate breaking changes.

Migrating to v0.9.x

Breaking Changes in v0.9.0

Version 0.9.0 introduced significant API changes to improve ergonomics and leverage a new reactive core based on alien-signals.
Major API ChangesVersion 0.9.0 is a major rewrite with breaking changes. Review this section carefully before upgrading.

Class Constructors → Factory Functions

The biggest change is moving from class constructors to factory functions:
// ❌ Old API (v0.8.x)
import { Store, Derived, Effect } from '@tanstack/store'

const store = new Store(initialValue)
const derived = new Derived(() => store.state * 2)
const effect = new Effect(() => {
  console.log(store.state)
})
// ✅ New API (v0.9.x)
import { createStore } from '@tanstack/store'

const store = createStore(initialValue)
const derived = createStore(() => store.state * 2)
const subscription = store.subscribe(() => {
  console.log(store.state)
})

Derived Stores

The Derived class has been replaced with computed createStore() calls:
// ❌ Old API
import { Store, Derived } from '@tanstack/store'

const count = new Store(10)
const doubled = new Derived(() => count.state * 2)
// ✅ New API
import { createStore } from '@tanstack/store'

const count = createStore(10)
const doubled = createStore(() => count.state * 2)
The new API is simpler: if you pass a function to createStore, it creates a derived (readonly) store. If you pass a value, it creates a mutable store.

Effects → Subscriptions

The Effect class has been removed in favor of the subscribe() method:
// ❌ Old API
import { Store, Effect } from '@tanstack/store'

const store = new Store(0)
const effect = new Effect(() => {
  console.log('Count:', store.state)
})

// Cleanup
effect.destroy()
// ✅ New API
import { createStore } from '@tanstack/store'

const store = createStore(0)
const { unsubscribe } = store.subscribe((value) => {
  console.log('Count:', value)
})

// Cleanup
unsubscribe()

Type Changes

Import types from the main package:
// ❌ Old API
import { Store, StoreInstance } from '@tanstack/store'

const store: StoreInstance<number> = new Store(0)
// ✅ New API
import { createStore, Store, ReadonlyStore } from '@tanstack/store'

// Mutable store
const store: Store<number> = createStore(0)

// Readonly store
const derived: ReadonlyStore<number> = createStore(() => store.state * 2)

Observable Interop

The subscription API now follows the Observable spec more closely:
// ❌ Old API
effect.subscribe({
  onNext: (value) => console.log(value),
  onError: (error) => console.error(error),
})
// ✅ New API
store.subscribe({
  next: (value) => console.log(value),
  error: (error) => console.error(error),
  complete: () => console.log('complete'),
})

// Or use the simpler callback form
store.subscribe((value) => console.log(value))

Step-by-Step Migration

1

Update Dependencies

Update your package.json to the latest version:
npm install @tanstack/store@latest
# or
yarn add @tanstack/store@latest
# or
pnpm add @tanstack/store@latest
2

Replace Store Constructor Calls

Find and replace all new Store() with createStore():
// Before
const store = new Store(initialValue)

// After
const store = createStore(initialValue)
3

Replace Derived Stores

Replace new Derived() with derived createStore():
// Before
const derived = new Derived(() => store.state * 2)

// After
const derived = createStore(() => store.state * 2)
4

Replace Effects with Subscriptions

Replace new Effect() with store.subscribe():
// Before
const effect = new Effect(() => {
  console.log(store.state)
})
effect.destroy() // cleanup

// After
const { unsubscribe } = store.subscribe((value) => {
  console.log(value)
})
unsubscribe() // cleanup
5

Update Type Imports

Update your type imports:
// Before
import { Store, StoreInstance } from '@tanstack/store'

// After
import { createStore, Store, ReadonlyStore } from '@tanstack/store'
6

Test Your Application

Run your test suite to ensure everything works:
npm test

Migrating from Other State Libraries

From Redux

TanStack Store can replace Redux with less boilerplate:
// Redux
import { createStore } from 'redux'

const reducer = (state = { count: 0 }, action) => {
  switch (action.type) {
    case 'INCREMENT':
      return { count: state.count + 1 }
    case 'DECREMENT':
      return { count: state.count - 1 }
    default:
      return state
  }
}

const store = createStore(reducer)
store.dispatch({ type: 'INCREMENT' })
// TanStack Store
import { createStore } from '@tanstack/store'

const store = createStore({ count: 0 })

const increment = () => {
  store.setState((s) => ({ ...s, count: s.count + 1 }))
}

const decrement = () => {
  store.setState((s) => ({ ...s, count: s.count - 1 }))
}

increment()

From Zustand

TanStack Store has a similar API to Zustand:
// Zustand
import { create } from 'zustand'

const useStore = create((set) => ({
  count: 0,
  increment: () => set((state) => ({ count: state.count + 1 })),
}))
// TanStack Store
import { createStore } from '@tanstack/store'
import { useStore } from '@tanstack/react-store'

const store = createStore({ count: 0 })

const increment = () => {
  store.setState((s) => ({ ...s, count: s.count + 1 }))
}

function Component() {
  const state = useStore(store)
  return <div onClick={increment}>{state.count}</div>
}

From Jotai

TanStack Store’s atoms work similarly to Jotai:
// Jotai
import { atom, useAtom } from 'jotai'

const countAtom = atom(0)
const doubledAtom = atom((get) => get(countAtom) * 2)
// TanStack Store
import { createAtom } from '@tanstack/store'
import { useStore } from '@tanstack/react-store'

const countAtom = createAtom(0)
const doubledAtom = createAtom(() => countAtom.get() * 2)

function Component() {
  const count = useStore(countAtom)
  const doubled = useStore(doubledAtom)
  return <div>{count} × 2 = {doubled}</div>
}

From MobX

TanStack Store provides similar reactivity with less boilerplate:
// MobX
import { makeObservable, observable, computed, action } from 'mobx'
import { observer } from 'mobx-react-lite'

class Store {
  count = 0

  constructor() {
    makeObservable(this, {
      count: observable,
      doubled: computed,
      increment: action,
    })
  }

  get doubled() {
    return this.count * 2
  }

  increment() {
    this.count++
  }
}
// TanStack Store
import { createStore } from '@tanstack/store'
import { useStore } from '@tanstack/react-store'

const countStore = createStore(0)
const doubledStore = createStore(() => countStore.state * 2)

const increment = () => {
  countStore.setState((c) => c + 1)
}

function Component() {
  const doubled = useStore(doubledStore)
  return <button onClick={increment}>{doubled}</button>
}

Common Migration Issues

Issue: Cannot call setState on readonly store

// ❌ Error: Property 'setState' does not exist
const derived = createStore(() => source.state * 2)
derived.setState(10)
Solution: Derived stores (created with a function) are readonly. Update the source store instead:
// ✅ Correct: Update the source
const source = createStore(5)
const derived = createStore(() => source.state * 2)
source.setState(10) // derived automatically updates to 20

Issue: Subscriptions fire immediately

Subscriptions fire once immediately on creation (this is intentional):
let count = 0
store.subscribe(() => {
  count++ // Fires immediately, then on each update
})
console.log(count) // 1 (not 0!)
Solution: Handle the initial call if needed:
let isFirst = true
store.subscribe(() => {
  if (isFirst) {
    isFirst = false
    return
  }
  // Handle subsequent updates
})

Issue: Type errors with generic stores

// ❌ Type error
const store = createStore<User>(null)
Solution: Use union types for nullable values:
// ✅ Correct
const store = createStore<User | null>(null)

Framework-Specific Migration

React

npm install @tanstack/react-store
import { createStore } from '@tanstack/store'
import { useStore } from '@tanstack/react-store'

const store = createStore(0)

function Component() {
  const count = useStore(store)
  return <div>{count}</div>
}

Vue

npm install @tanstack/vue-store
import { createStore } from '@tanstack/store'
import { useStore } from '@tanstack/vue-store'

const store = createStore(0)

export default {
  setup() {
    const count = useStore(store)
    return { count }
  },
}

Svelte

npm install @tanstack/svelte-store
import { createStore } from '@tanstack/store'
import { useStore } from '@tanstack/svelte-store'

const store = createStore(0)
const count = useStore(store)
<script>
  import { count } from './store'
</script>

<div>{$count}</div>

Version History

v0.9.1 (Latest)

  • Fix: Derived createStore now returns readonly store (#278)

v0.9.0

  • Breaking: new Store()createStore() (#265)
  • Breaking: new Derived() → derived createStore()
  • Breaking: new Effect()store.subscribe()
  • New: Uses alien-signals for efficient reactivity

v0.8.1

  • Fix: Issues with Derived Fields not Retriggering (#274)

Getting Help

If you encounter migration issues:

GitHub Discussions

Ask questions and get help from the community

GitHub Issues

Report bugs or migration problems

Discord

Join the TanStack Discord for real-time help

Twitter

Follow @tanstack for updates

Next Steps