tanstack-store — скилл для Claude Code: реактивный стор и установка
Скилл по TanStack Store: реактивный стор без привязки к фреймворку, адаптеры для React, Vue, Solid, Angular и Svelte. Пригодится, когда агент пишет состояние на @tanstack/store.
npx skills add tanstack-skills/tanstack-skills --skill tanstack-store -a claude-codeЧто делает tanstack-store
Скилл даёт агенту справочник по API TanStack Store. Внутри — Store для состояния, Derived для вычисляемых значений, Effect для побочных эффектов и batch для атомарных обновлений. Обновление состояния идёт через setState с функцией-апдейтером, подписка — через subscribe с возвратом функции отписки.
Отдельный блок про фреймворк-адаптеры. Для React это useStore(store, selector?, equalityFn?) из @tanstack/react-store, для Vue, Solid, Angular и Svelte — свои пакеты и хуки. Есть раздел про shallow для селекторов, возвращающих объекты или массивы: без него компонент перерисовывается на любое изменение состояния, потому что ссылка на объект новая.
Важная деталь, на которой легко споткнуться: у Derived и Effect нужно вызывать mount(), иначе они не работают. Функцию размонтирования возвращает сам mount, и в React её принято отдавать из useEffect. Текст обрезан, поэтому полный список практик может быть шире показанного.
Какие задачи решает
- завести общий стор без Redux и лишних обёрток
- посчитать производное значение из двух сторов
- сгруппировать несколько обновлений в одно уведомление подписчиков
- подписать React-компонент на часть состояния через селектор
- повесить побочный эффект с очисткой таймера
Примеры запросов агенту
- Сделай стор счётчика на
@tanstack/storeи подключи его в React черезuseStore - Добавь
Derived, который фильтрует список по строке поиска, и не забудьmount() - Оберни три обновления сторов в
batch, чтобы подписчики сработали один раз
Как установить tanstack-store
1Через Skills CLI (Vercel)
npx skills add tanstack-skills/tanstack-skills --skill tanstack-store -a claude-code2Вручную: положить файлы
~/.claude/skills/tanstack-store/SKILL.mdПапка скилла целиком; для одного проекта — .claude/skills/tanstack-store/
1Через Skills CLI (Vercel)
npx skills add tanstack-skills/tanstack-skills --skill tanstack-store -a codex2Вручную: положить файлы
.agents/skills/tanstack-store/SKILL.mdДля всех проектов — ~/.agents/skills/
1Через Skills CLI (Vercel)
npx skills add tanstack-skills/tanstack-skills --skill tanstack-store -a cursor2Вручную: положить файлы
.cursor/skills/tanstack-store/SKILL.mdCursor читает и .agents/skills/, и .claude/skills/
1GitHub CLI
gh skill install tanstack-skills/tanstack-skills tanstack-store2Вручную: положить файлы
.github/skills/tanstack-store/SKILL.md1Через Skills CLI (Vercel)
npx skills add tanstack-skills/tanstack-skills --skill tanstack-store -a gemini-cli2Командой платформы
gemini skills install https://github.com/tanstack-skills/tanstack-skills --path plugins/tanstack-store/skills/tanstack-storeВручную: положить файлы
.devin/skills/tanstack-store/SKILL.mdСтарый путь .windsurf/skills/ тоже читается
Через Skills CLI (Vercel)
npx skills add tanstack-skills/tanstack-skills --skill tanstack-store -a opencodeClaude.ai
Упакуйте папку в zip и загрузите в Settings → Capabilities → Skills
По шагам, с командами для каждой платформы: Как установить скилл в Claude Code: пять способов
Вопросы о tanstack-store
Как установить tanstack-store в Claude?
Выполни npx skills add tanstack-skills/tanstack-skills --skill tanstack-store -a claude-code. Файл скилла окажется в ~/.claude/skills/tanstack-store/SKILL.md.
Что делает tanstack-store?
Учит агента работать с реактивным стором TanStack: Store, Derived, Effect, batch и хуки-адаптеры для React, Vue, Solid, Angular и Svelte.
Почему Derived не обновляется?
Скорее всего, не вызван mount(). Без него Derived и Effect не активны, а возвращённую функцию нужно вызвать при размонтировании.
Исходный текст
SKILL.md · MIT
---
name: tanstack-store
description: Framework-agnostic, immutable reactive data store with framework adapters for React, Vue, Solid, Angular, and Svelte.
---
## Overview
TanStack Store is a lightweight reactive store (signals-like) that powers the internals of TanStack libraries. It provides `Store` for state, `Derived` for computed values, `Effect` for side effects, and `batch` for atomic updates. Framework adapters provide reactive hooks.
**Core:** `@tanstack/store`
**React:** `@tanstack/react-store`
## Installation
```bash
npm install @tanstack/store @tanstack/react-store
```
## Store
### Creating a Store
```typescript
import { Store } from '@tanstack/store'
const countStore = new Store(0)
const userStore = new Store<{ name: string; email: string }>({
name: 'Alice',
email: '[email protected]',
})
```
### Updating State
```typescript
// Function updater (immutable update)
countStore.setState((prev) => prev + 1)
userStore.setState((prev) => ({ ...prev, name: 'Bob' }))
```
### Subscribing to Changes
```typescript
const unsub = countStore.subscribe(() => {
console.log('Count:', countStore.state)
})
// Cleanup
unsub()
```
### Store Options
```typescript
const store = new Store(initialState, {
// Custom update function
updateFn: (prevValue) => (updater) => {
return updater(prevValue) // custom logic
},
// Callback on subscribe
onSubscribe: (listener, store) => {
console.log('New subscriber')
return () => console.log('Unsubscribed')
},
// Callback on every update
onUpdate: () => {
console.log('State updated:', store.state)
},
})
```
### Store Properties
```typescript
store.state // Current state
store.prevState // Previous state
store.listeners // Set of listener callbacks
```
## Derived (Computed Values)
```typescript
import { Store, Derived } from '@tanstack/store'
const count = new Store(5)
const multiplier = new Store(2)
const doubled = new Derived({
deps: [count, multiplier],
fn: ({ currDepVals }) => currDepVals[0] * currDepVals[1],
})
// MUST mount to activate
const unmount = doubled.mount()
console.log(doubled.state) // 10
count.setState(() => 10)
console.log(doubled.state) // 20
// Cleanup
unmount()
```
### Derived with Previous Value
```typescript
const accumulated = new Derived({
deps: [count],
fn: ({ prevVal, currDepVals }) => {
return currDepVals[0] + (prevVal ?? 0)
},
})
```
### Chaining Derived
```typescript
const filtered = new Derived({
deps: [dataStore, filterStore],
fn: ({ currDepVals }) => currDepVals[0].filter(matchesFilter(currDepVals[1])),
})
const sorted = new Derived({
deps: [filtered, sortStore],
fn: ({ currDepVals }) => [...currDepVals[0]].sort(comparator(currDepVals[1])),
})
const paginated = new Derived({
deps: [sorted, pageStore],
fn: ({ currDepVals }) => currDepVals[0].slice(
currDepVals[1].offset,
currDepVals[1].offset + currDepVals[1].limit,
),
})
```
## Effect (Side Effects)
```typescript
import { Store, Effect } from '@tanstack/store'
const count = new Store(0)
const logger = new Effect({
deps: [count],
fn: () => {
console.log('Count changed:', count.state)
// Optionally return cleanup function
return () => console.log('Cleaning up')
},
eager: false, // true = run immediately on mount
})
const unmount = logger.mount()
count.setState(() => 1) // logs: "Count changed: 1"
unmount()
```
### Effect with Cleanup
```typescript
const timerEffect = new Effect({
deps: [intervalStore],
fn: () => {
const id = setInterval(() => { /* ... */ }, intervalStore.state)
return () => clearInterval(id) // cleanup on next run or unmount
},
})
```
## Batch
Group multiple updates into one notification:
```typescript
import { batch } from '@tanstack/store'
// Subscribers fire only once with final state
batch(() => {
countStore.setState(() => 1)
nameStore.setState(() => 'Alice')
settingsStore.setState((prev) => ({ ...prev, theme: 'dark' }))
})
```
## React Integration
### useStore Hook
```tsx
import { useStore } from '@tanstack/react-store'
// Subscribe to full state
function Counter() {
const count = useStore(countStore)
return <button onClick={() => countStore.setState((c) => c + 1)}>{count}</button>
}
// Subscribe with selector (performance optimization)
function UserName() {
const name = useStore(userStore, (state) => state.name)
return <span>{name}</span>
}
// Subscribe to Derived
function DoubledDisplay() {
const value = useStore(doubledDerived)
return <span>{value}</span>
}
```
### shallow Equality Function
Prevents re-renders when selector returns structurally-equal objects:
```tsx
import { useStore } from '@tanstack/react-store'
import { shallow } from '@tanstack/react-store'
function TodoList() {
// Without shallow: re-renders on ANY state change (new object ref)
// With shallow: only re-renders when items actually change
const items = useStore(todosStore, (state) => state.items, shallow)
return <ul>{items.map(/* ... */)}</ul>
}
```
### Mounting Derived/Effect in React
```tsx
function MyComponent() {
useEffect(() => {
const unmountDerived = myDerived.mount()
const unmountEffect = myEffect.mount()
return () => {
unmountDerived()
unmountEffect()
}
}, [])
const value = useStore(myDerived)
return <span>{value}</span>
}
```
## Module-Level Store Pattern
```typescript
// stores/counter.ts
import { Store, Derived } from '@tanstack/store'
export const counterStore = new Store(0)
export const doubledCount = new Derived({
deps: [counterStore],
fn: ({ currDepVals }) => currDepVals[0] * 2,
})
// Actions as plain functions
export function increment() {
counterStore.setState((c) => c + 1)
}
export function reset() {
counterStore.setState(() => 0)
}
```
## Framework Adapters
| Framework | Package | Hook/Composable |
|-----------|---------|-----------------|
| React | `@tanstack/react-store` | `useStore(store, selector?, equalityFn?)` |
| Vue | `@tanstack/vue-store` | `useStore(store, selector?)` (returns computed ref) |
| Solid | `@tanstack/solid-store` | `useStore(store, selector?)` (returns signal) |
| Angular | `@tanstack/angular-store` | `injectStore(store, selector?)` (returns signal) |
| Svelte | `@tanstack/svelte-store` | `useStore(store, selector?)` (returns $state) |
## Best Practices
1. **Define stores at module level** - they're singletons
2. **Use selectors** in `useStore` to prevent unnecessary re-renders
3. **Use `shallow`** when selectors return objects/arrays
4. **Always call `mount()`** on Derived and Effect instances
5. **Always clean up** unmount functions (especially in React useEffect)
6. **Never mutate state directly** - always use `setState`
7. **Use `batch`** for multiple related updates
8. **Use Derived chains** for data transformations (filter -> sort -> paginate)
9. **Return cleanup functions** from Effect `fn` for timers/listeners
10. **Select primitives** when possible (no equality fn needed)
## Common Pitfalls
- Forgetting to `mount()` Derived/Effect (they won't activate)
- Not cleaning up subscriptions/unmount functions (memory leaks)
- Mutating `store.state` directly instead of using `setState`
- Creating new object references in selectors without `shallow`
- Using `useStore` without a selector (subscribes to everything)
- Forgetting `eager: true` when Effect should run immediately
© tanstack-skills · MIT · tanstack-skills/tanstack-skills