원클릭으로
tanstack-store
Framework-agnostic, immutable reactive data store with framework adapters for React, Vue, Solid, Angular, and Svelte.
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
메뉴
Framework-agnostic, immutable reactive data store with framework adapters for React, Vue, Solid, Angular, and Svelte.
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
SOC 직업 분류 기준
Design engineering principles for making interfaces feel polished. Use when building UI components, reviewing frontend code, implementing animations, hover states, shadows, borders, typography, micro-interactions, enter/exit animations, or any visual detail work. Triggers on UI polish, design details, "make it feel better", "feels off", stagger animations, border radius, optical alignment, font smoothing, tabular numbers, image outlines, box shadows.
Project scaffolding CLI with 30+ integrations, custom templates, and MCP server for AI agents.
Opinionated toolkit for building, versioning, and publishing high-quality JavaScript/TypeScript packages.
Reactive client-first store for your API with collections, live queries, and optimistic mutations.
Centralized, extensible devtools panel for TanStack libraries with a plugin architecture.
Framework-agnostic debouncing, throttling, rate limiting, queuing, and batching utilities.
| name | tanstack-store |
| description | Framework-agnostic, immutable reactive data store with framework adapters for React, Vue, Solid, Angular, and Svelte. |
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
npm install @tanstack/store @tanstack/react-store
import { Store } from "@tanstack/store";
const countStore = new Store(0);
const userStore = new Store<{ name: string; email: string }>({
name: "Alice",
email: "alice@example.com",
});
// Function updater (immutable update)
countStore.setState((prev) => prev + 1);
userStore.setState((prev) => ({ ...prev, name: "Bob" }));
const unsub = countStore.subscribe(() => {
console.log("Count:", countStore.state);
});
// Cleanup
unsub();
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.state; // Current state
store.prevState; // Previous state
store.listeners; // Set of listener callbacks
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();
const accumulated = new Derived({
deps: [count],
fn: ({ prevVal, currDepVals }) => {
return currDepVals[0] + (prevVal ?? 0);
},
});
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),
});
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();
const timerEffect = new Effect({
deps: [intervalStore],
fn: () => {
const id = setInterval(() => {
/* ... */
}, intervalStore.state);
return () => clearInterval(id); // cleanup on next run or unmount
},
});
Group multiple updates into one notification:
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" }));
});
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>;
}
Prevents re-renders when selector returns structurally-equal objects:
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>;
}
function MyComponent() {
useEffect(() => {
const unmountDerived = myDerived.mount();
const unmountEffect = myEffect.mount();
return () => {
unmountDerived();
unmountEffect();
};
}, []);
const value = useStore(myDerived);
return <span>{value}</span>;
}
// 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 | 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) |
useStore to prevent unnecessary re-rendersshallow when selectors return objects/arraysmount() on Derived and Effect instancessetStatebatch for multiple related updatesfn for timers/listenersmount() Derived/Effect (they won't activate)store.state directly instead of using setStateshallowuseStore without a selector (subscribes to everything)eager: true when Effect should run immediately