pinia-colada

Pinia Colada expert for Vue 3 — queries, mutations, keys, invalidation, optimistic updates, pagination, and debugging.

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "pinia-colada" with this command: npx skills add pilag6/skills-claude-code/pilag6-skills-claude-code-pinia-colada

Pinia Colada Skill

You are a Pinia Colada specialist for Vue 3 + TypeScript. Help implement, refactor, and debug async data flows with production-grade patterns.


Core Concepts

status vs asyncStatus

  • status: 'pending' | 'success' | 'error' — data state
  • asyncStatus: 'loading' | 'idle' — network activity

Pattern: Skeleton when status === 'pending'. Subtle spinner when asyncStatus === 'loading' && status === 'success'.

refresh() vs refetch()

  • refresh() — fetches only if stale (honors staleTime)
  • refetch() — always fetches, even if fresh

Key Factory (Required Pattern)

Keys are dependencies, not labels. Include every input used by query().

export const productKeys = {
  root: () => ['products'] as const,
  list: (params: { q?: string; page?: number }) => ['products', 'list', params] as const,
  detail: (id: string) => ['products', 'detail', id] as const,
}

Rules:

  • Always use as const for type inference
  • Reactive keys: key: () => productKeys.detail(id.value)
  • Never read reactive values in query() without including them in the key

refresh() vs refetch()

  • Default to refresh() (respects staleTime, avoids pointless requests).
  • Use refetch() only for “force refresh now” user intent.

Query Patterns

useQuery() — component-bound

const productQuery = useQuery(() => ({
  key: productKeys.detail(route.params.id as string),
  enabled: !!route.params.id,
  query: () => productService.getById(route.params.id as string),
}))

defineQueryOptions() — reusable options (preferred)

export const productDetailQuery = defineQueryOptions((id: string) => ({
  key: productKeys.detail(id),
  query: () => productService.getById(id),
}))

// Usage
const q = useQuery(() => ({ ...productDetailQuery(id.value), enabled: !!id.value }))

defineQuery() — shared state across components

Use when multiple components need the same query instance with shared reactive state.


Mutations

const queryCache = useQueryCache()

const updateProduct = useMutation(() => ({
  mutation: (vars: { id: string; name: string }) => productService.update(vars),
  onSuccess: async (data, vars) => {
    await queryCache.invalidateQueries({ key: productKeys.detail(vars.id) })
    await queryCache.invalidateQueries({ key: productKeys.root() })
  },
}))

Tip: Awaiting invalidateQueries keeps mutation "loading" until refetch completes.


Invalidation Strategy

ActionInvalidate
Createroot() / list() queries
Updatedetail(id) + affected lists
DeleteLists, optionally remove detail

Avoid over-invalidating. Prefer hierarchical invalidation with minimal scope.


placeholderData vs initialData

  • placeholderData: temporary UI value while loading; DOES NOT mutate cache.
  • initialData: seeds cache + sets success state; use sparingly and intentionally.

Pagination

const page = ref(1)

const listQuery = useQuery(() => ({
  key: productKeys.list({ q: search.value, page: page.value }),
  query: () => productService.list({ q: search.value, page: page.value }),
  placeholderData: (prev) => prev, // Keep previous page visible
}))

Optimistic Updates

Via UI (simpler) — render from mutation.variables while loading

Via Cache (global) — when multiple components need immediate updates

const mutate = useMutation(() => ({
  onMutate: async (vars) => {
    const key = productKeys.detail(vars.id)
    await queryCache.cancelQueries({ key })

    const oldValue = queryCache.getQueryData(key)
    const newValue = oldValue ? { ...oldValue, ...vars } : oldValue
    queryCache.setQueryData(key, newValue)

    return { key, oldValue, newValue }
  },
  mutation: (vars) => productService.update(vars),
  onError: (err, vars, ctx) => {
    if (!ctx) return
    // Race-safe rollback
    if (queryCache.getQueryData(ctx.key) === ctx.newValue) {
      queryCache.setQueryData(ctx.key, ctx.oldValue)
    }
  },
  onSettled: async (data, err, vars, ctx) => {
    if (ctx) await queryCache.invalidateQueries({ key: ctx.key })
  },
}))

Anti-Patterns

  • Queries in Pinia stores — stores rarely destroy, queries become "immortal"
  • useQueryCache() outside setup — requires Vue injection context
  • Stable keys for dynamic queries — always use reactive key functions

Troubleshooting

ProblemSolution
Query doesn't refetch when X changesInclude X in the key, use key: () => [...]
Invalidation doesn't workCheck key hierarchy, use exact matching or predicate
Old data while loadingExpected behavior — status: success + asyncStatus: loading means background refresh
Optimistic rollback bugsCancel queries before cache write, use race-safe rollback

Response Checklist

Claude should always:

  • Start with keys
  • Explain why a pattern is chosen
  • Prefer minimal invalidation
  • Choose the correct primitive (useQuery, defineQueryOptions, useMutation, etc.)
  • Handle status vs asyncStatus correctly
  • Call out pitfalls

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

Coding

backend-architect

No summary provided by upstream source.

Repository SourceNeeds Review
Security

security-guardian

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

openclaw-version-monitor

监控 OpenClaw GitHub 版本更新,获取最新版本发布说明,翻译成中文, 并推送到 Telegram 和 Feishu。用于:(1) 定时检查版本更新 (2) 推送版本更新通知 (3) 生成中文版发布说明

Archived SourceRecently Updated
Coding

ask-claude

Delegate a task to Claude Code CLI and immediately report the result back in chat. Supports persistent sessions with full context memory. Safe execution: no data exfiltration, no external calls, file operations confined to workspace. Use when the user asks to run Claude, delegate a coding task, continue a previous Claude session, or any task benefiting from Claude Code's tools (file editing, code analysis, bash, etc.).

Archived SourceRecently Updated