TanStack Store provides Vue support through the @tanstack/vue-store package, which integrates with Vue’s reactivity system using composables. It supports both Vue 2.7+ and Vue 3.
Installation
Install the Vue adapter package:
npm install @tanstack/vue-store
The @tanstack/vue-store package re-exports everything from @tanstack/store, so you only need to install the Vue package.
Vue Version Compatibility
The package uses vue-demi to support multiple Vue versions:
- Vue 3.x (recommended)
- Vue 2.7+
- Vue 2.6 with
@vue/composition-api
Basic Usage
The primary way to use TanStack Store in Vue is through the useStore composable:
<script setup lang="ts">
import { createStore, useStore } from '@tanstack/vue-store'
// Create a store
const counterStore = createStore({
count: 0,
})
// Subscribe to the store
const count = useStore(counterStore, (state) => state.count)
const increment = () => {
counterStore.setState((prev) => ({ count: prev.count + 1 }))
}
</script>
<template>
<div>
<p>Count: {{ count }}</p>
<button @click="increment">Increment</button>
</div>
</template>
The useStore Composable
The useStore composable subscribes your component to store updates and returns a readonly ref that automatically updates.
Signature
function useStore<TState, TSelected>(
store: Atom<TState> | ReadonlyAtom<TState>,
selector?: (state: TState) => TSelected,
options?: { equal?: (a: TSelected, b: TSelected) => boolean }
): Readonly<Ref<TSelected>>
Parameters
store - The store instance to subscribe to
selector - (Optional) A function that selects which part of the state you need. Defaults to returning the entire state
options.equal - (Optional) A custom equality function. Defaults to shallow
Return Value
Returns a readonly Vue Ref containing the selected state. Use .value to access the state value in script, or directly in templates.
Selector Optimization
The selector function allows you to subscribe to only the parts of state you need:
<script setup lang="ts">
import { createStore, useStore } from '@tanstack/vue-store'
const appStore = createStore({
user: { name: 'Alice', age: 30 },
settings: { theme: 'dark', notifications: true },
})
// Only updates when user.name changes
const userName = useStore(appStore, (state) => state.user.name)
// Only updates when settings change
const settings = useStore(appStore, (state) => state.settings)
</script>
<template>
<div>
<h1>Welcome, {{ userName }}!</h1>
<div>
<p>Theme: {{ settings.theme }}</p>
<p>Notifications: {{ settings.notifications ? 'On' : 'Off' }}</p>
</div>
</div>
</template>
Custom Equality Functions
For complex state selections, provide a custom equality function:
<script setup lang="ts">
import { createStore, useStore } from '@tanstack/vue-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)
}
// Use deep equality for array comparison
const activeTodos = useStore(
todoStore,
(state) => state.todos.filter((todo) => !todo.completed),
{ equal: deepEqual }
)
</script>
<template>
<ul>
<li v-for="todo in activeTodos" :key="todo.id">
{{ todo.text }}
</li>
</ul>
</template>
Shallow Equality
The package exports a shallow utility for shallow object comparison (this is the default):
<script setup lang="ts">
import { createStore, useStore, shallow } from '@tanstack/vue-store'
const userStore = createStore({
profile: { name: 'Alice', email: '[email protected]' },
preferences: { theme: 'dark' },
})
// Explicitly use shallow comparison (this is the default)
const profile = useStore(
userStore,
(state) => state.profile,
{ equal: shallow }
)
</script>
<template>
<div>
<h2>{{ profile.name }}</h2>
<p>{{ profile.email }}</p>
</div>
</template>
Complete Example: Todo App
Here’s a complete Todo application using Vue 3 Composition API:
<script setup lang="ts">
import { ref } from 'vue'
import { createStore, useStore } from '@tanstack/vue-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 }))
}
// Component state
const input = ref('')
// Subscribe to store
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 = () => {
if (input.value.trim()) {
addTodo(input.value)
input.value = ''
}
}
</script>
<template>
<div>
<h1>Todo App</h1>
<form @submit.prevent="handleSubmit">
<input
v-model="input"
placeholder="What needs to be done?"
/>
<button type="submit">Add</button>
</form>
<div>
<button @click="setFilter('all')">All</button>
<button @click="setFilter('active')">Active</button>
<button @click="setFilter('completed')">Completed</button>
</div>
<ul>
<li v-for="todo in todos" :key="todo.id">
<input
type="checkbox"
:checked="todo.completed"
@change="toggleTodo(todo.id)"
/>
<span :style="{ textDecoration: todo.completed ? 'line-through' : 'none' }">
{{ todo.text }}
</span>
</li>
</ul>
</div>
</template>
Options API Support
While the Composition API is recommended, you can use stores with the Options API:
<script lang="ts">
import { defineComponent } from 'vue'
import { createStore, useStore } from '@tanstack/vue-store'
const counterStore = createStore({ count: 0 })
export default defineComponent({
setup() {
const count = useStore(counterStore, (state) => state.count)
return {
count,
increment: () => counterStore.setState((prev) => ({ count: prev.count + 1 }))
}
},
})
</script>
<template>
<div>
<p>Count: {{ count }}</p>
<button @click="increment">Increment</button>
</div>
</template>
Vue-Specific Considerations
Reactivity System Integration
The Vue adapter integrates deeply with Vue’s reactivity system:
- Uses
watch to subscribe to store changes
- Returns readonly refs to prevent accidental mutations
- Automatically cleans up subscriptions when components unmount
Server-Side Rendering (SSR)
TanStack Store works with Vue SSR. Create stores at module level and initialize them with server data:
import { createStore } from '@tanstack/vue-store'
// Create store at module level
export const appStore = createStore({
data: null,
isLoading: false,
})
// Initialize with SSR data
export function initializeStore(initialData: any) {
appStore.setState({ data: initialData, isLoading: false })
}
Nuxt Integration
For Nuxt 3, create a plugin to initialize stores:
// plugins/store.ts
import { appStore } from '~/stores/app'
export default defineNuxtPlugin(() => {
// Initialize your stores here if needed
})
- Use selective subscriptions: Subscribe only to the state you need
- Leverage the default shallow equality: It works well for most cases
- Create focused stores: Multiple small stores perform better than one large store
- Use computed refs: For derived state within components, use Vue’s
computed
Derived Stores
Create derived stores that react to other stores:
<script setup lang="ts">
import { createStore, useStore } from '@tanstack/vue-store'
const countStore = createStore(0)
const doubleStore = createStore(() => ({ value: countStore.state * 2 }))
const count = useStore(countStore, (state) => state)
const double = useStore(doubleStore, (state) => state.value)
const increment = () => {
countStore.setState((prev) => prev + 1)
}
</script>
<template>
<div>
<p>Count: {{ count }}</p>
<p>Double: {{ double }}</p>
<button @click="increment">Increment</button>
</div>
</template>
API Reference
For detailed API documentation, see:
-
- useStore - Vue composable for subscribing to stores
-
- shallow - Shallow equality comparison utility
- createStore - Core store creation API
TypeScript Support
The Vue adapter provides full TypeScript support:
<script setup lang="ts">
import { createStore, useStore } from '@tanstack/vue-store'
interface AppState {
user: { name: string; id: number }
isAuthenticated: boolean
}
const store = createStore<AppState>({
user: { name: '', id: 0 },
isAuthenticated: false,
})
// TypeScript infers the correct types
const userName = useStore(store, (state) => state.user.name)
// userName is Readonly<Ref<string>>
</script>
<template>
<div>{{ userName }}</div>
</template>