TanStack Store is built with TypeScript from the ground up, providing excellent type inference and type safety throughout your application. This guide covers TypeScript patterns, generic types, and best practices for maintaining type safety.
TanStack Store automatically infers types from initial values:
import { createStore } from '@tanstack/store'// Type is inferred as Store<number>const countStore = createStore(0)// ✅ Type-safe: accepts numbercountStore.setState(10)// ❌ Type error: Type 'string' is not assignable to type 'number'countStore.setState('hello')
Derived stores automatically infer their return type:
const countStore = createStore(10)// Type is inferred as ReadonlyStore<number>const doubledStore = createStore(() => countStore.state * 2)// Type is inferred as ReadonlyStore<string>const messageStore = createStore(() => { const count = countStore.state return `Count is ${count}`})
Function updaters provide the previous state with full type safety:
interface Counter { count: number lastUpdated: Date}const counterStore = createStore<Counter>({ count: 0, lastUpdated: new Date(),})// ✅ Type-safe: prev is typed as CountercounterStore.setState((prev) => ({ ...prev, count: prev.count + 1, lastUpdated: new Date(),}))// ❌ Type error: Property 'invalid' does not exist on type 'Counter'counterStore.setState((prev) => ({ ...prev, invalid: true,}))
TanStack Store uses TypeScript’s NoInfer utility type internally to prevent unintended type inference:
// From source: packages/store/src/store.ts:6constructor(getValue: (prev?: NoInfer<T>) => T)// This prevents TypeScript from inferring T from the function parameter// ensuring the type flows from the return value instead
import { expectType } from 'vitest'import { createStore, Store, ReadonlyStore } from '@tanstack/store'// Test that types are inferred correctlyconst numStore = createStore(0)expectType<Store<number>>(numStore)const strStore = createStore('hello')expectType<Store<string>>(strStore)// Test readonly storesconst derivedStore = createStore(() => numStore.state * 2)expectType<ReadonlyStore<number>>(derivedStore)// Type error tests can be done with @ts-expect-error// @ts-expect-error - Cannot call setState on readonly storederivedStore.setState(10)
// ❌ Error: Type 'string' is not assignable to type 'number'const store = createStore(0)store.setState('hello')// ✅ Solution: Ensure the value matches the store's typestore.setState(42)
interface User { name: string}const userStore = createStore<User>({ name: 'John' })// ❌ Error: Property 'age' does not exist on type 'User'userStore.setState({ name: 'Jane', age: 30 })// ✅ Solution: Extend the interface or use a different typeinterface ExtendedUser extends User { age: number}