All skills
asyrafhussin avatar

/state-management

@10bb16e

React Query and Zustand patterns for state management. Use when implementing data fetching, caching, mutations, or client-side state. Triggers on tasks involving useQuery, useMutation, Zustand stores, caching, or state management.

Use this Skill: https://skilld.dev/gh/asyrafhussin/agent-skills/state-management

This session only. Nothing lands on disk.

rulesrq-usequery.md

≈1.3k tokens on demand. Your agent reads this file only when SKILL.md points to it.

useQuery Hook Patterns

Impact: CRITICAL

Why It Matters

useQuery is the primary hook for fetching data. Proper usage ensures efficient caching, automatic refetching, and proper loading/error states.

Basic Usage

import { useQuery } from '@tanstack/react-query'

function Posts() {
  const {
    data,           // The fetched data
    isLoading,      // First load, no data yet
    isFetching,     // Any fetch, including background
    isError,        // Fetch failed
    error,          // Error object
    isSuccess,      // Fetch succeeded
    refetch,        // Manually refetch
  } = useQuery({
    queryKey: ['posts'],
    queryFn: fetchPosts,
  })

  if (isLoading) return <Spinner />
  if (isError) return <Error message={error.message} />

  return (
    <ul>
      {data?.map((post) => (
        <li key={post.id}>{post.title}</li>
      ))}
    </ul>
  )
}

Query Function

// Simple fetch
const queryFn = async () => {
  const response = await fetch('/api/posts')
  if (!response.ok) {
    throw new Error('Network response was not ok')
  }
  return response.json()
}

// With axios
const queryFn = async () => {
  const { data } = await axios.get('/api/posts')
  return data
}

// With parameters (from query key)
const queryFn = async ({ queryKey }) => {
  const [_key, postId] = queryKey
  const { data } = await axios.get(`/api/posts/${postId}`)
  return data
}

Query Key Best Practices

// ✅ Array with unique identifiers
queryKey: ['posts']
queryKey: ['posts', 'list']
queryKey: ['posts', 'detail', postId]
queryKey: ['posts', 'list', { status: 'published', page: 1 }]

// ✅ Query key factory pattern
export const postKeys = {
  all: ['posts'] as const,
  lists: () => [...postKeys.all, 'list'] as const,
  list: (filters: Filters) => [...postKeys.lists(), filters] as const,
  details: () => [...postKeys.all, 'detail'] as const,
  detail: (id: number) => [...postKeys.details(), id] as const,
}

// Usage
useQuery({
  queryKey: postKeys.detail(5),
  queryFn: () => fetchPost(5),
})

Configuration Options

useQuery({
  queryKey: ['posts'],
  queryFn: fetchPosts,

  // Caching
  staleTime: 1000 * 60 * 5,    // Data fresh for 5 minutes
  gcTime: 1000 * 60 * 30,      // Keep in cache for 30 minutes

  // Refetching
  refetchOnWindowFocus: true,   // Refetch when window regains focus
  refetchOnMount: true,         // Refetch when component mounts
  refetchOnReconnect: true,     // Refetch when network reconnects
  refetchInterval: 1000 * 60,   // Poll every minute

  // Retry
  retry: 3,                     // Retry failed requests 3 times
  retryDelay: (attempt) => Math.min(1000 * 2 ** attempt, 30000),

  // Initial data
  initialData: [],              // Data before first fetch
  placeholderData: previousData, // Show while fetching

  // Conditional
  enabled: !!userId,            // Only fetch if userId exists
})

Handling States

function Posts() {
  const { data, status, fetchStatus } = useQuery({
    queryKey: ['posts'],
    queryFn: fetchPosts,
  })

  // status: 'pending' | 'error' | 'success'
  // fetchStatus: 'fetching' | 'paused' | 'idle'

  // First load (no cached data)
  if (status === 'pending') {
    return <Skeleton />
  }

  // Error state
  if (status === 'error') {
    return <ErrorMessage />
  }

  // Success with potential background refresh
  return (
    <>
      {fetchStatus === 'fetching' && <RefreshIndicator />}
      <PostList posts={data} />
    </>
  )
}

TypeScript

interface Post {
  id: number
  title: string
  body: string
}

// Type the response
const { data } = useQuery<Post[]>({
  queryKey: ['posts'],
  queryFn: fetchPosts,
})

// Type error as well
const { data, error } = useQuery<Post[], Error>({
  queryKey: ['posts'],
  queryFn: fetchPosts,
})

// With custom error type
interface ApiError {
  message: string
  code: number
}

const { data, error } = useQuery<Post[], ApiError>({
  queryKey: ['posts'],
  queryFn: fetchPosts,
})

Custom Hook Pattern

// hooks/usePosts.ts
export function usePosts(filters?: PostFilters) {
  return useQuery({
    queryKey: postKeys.list(filters ?? {}),
    queryFn: () => fetchPosts(filters),
    staleTime: 1000 * 60 * 5,
  })
}

export function usePost(id: number) {
  return useQuery({
    queryKey: postKeys.detail(id),
    queryFn: () => fetchPost(id),
    enabled: !!id,
  })
}

// Usage
function PostPage({ id }: { id: number }) {
  const { data: post, isLoading } = usePost(id)
  // ...
}

Dependent Queries

// Fetch user first, then their posts
function UserPosts({ userId }: { userId: number }) {
  const { data: user } = useQuery({
    queryKey: ['user', userId],
    queryFn: () => fetchUser(userId),
  })

  const { data: posts } = useQuery({
    queryKey: ['posts', { authorId: user?.id }],
    queryFn: () => fetchPostsByAuthor(user!.id),
    enabled: !!user?.id,  // Only run after user is loaded
  })

  // ...
}

Impact

  • Automatic caching and deduplication
  • Background refetching
  • Proper loading/error states
  • Optimized performance

Source: SKILL.md on GitHub

No alerts16d5 checks · Risk SAFE
  • Gen Agent Trust Hub16d

    This skill provides secure and comprehensive documentation for managing application state using React Query and Zustand. It includes critical security warnings against storing sensitive data in local storage and promotes best practices for data fetching, caching, and error handling. No malicious patterns or security vulnerabilities were detected.

  • Socket16d

    No alerts

  • Snyk16d

    Risk: LOW · No issues

  • Runlayer6mo

    32 files scanned · No issues

  • ZeroLeaks5mo

    Score: 93/100 · 2 sections analyzed

Signed by skilld at 10bb16e. This ties the file your Agent reads to that commit on GitHub. It does not review the instructions.

Last checked against GitHub last month.

Steadyupdated 7 months ago
metadata
{
  "author": "agent-skills",
  "version": "1.1.0"
}

README badge

README badge for asyrafhussin/agent-skills/state-management