askill
react-query-v3-patterns

react-query-v3-patternsSafety 90Repository

WHAT: React Query v3.39.0 patterns for web data fetching with OpenTelemetry. WHEN: using useQuery v3, useMutation v3, implementing caching on web. KEYWORDS: react-query, v3, useQuery, useMutation, useFetch, cache, web, tracing, RequestIds.

1 stars
1.2k downloads
Updated 1/27/2026

Package Files

Loading files...
SKILL.md

React Query v3 Patterns - Web

Data fetching patterns using react-query v3.39.0 (NOT TanStack Query v5) with custom useFetch hook and OpenTelemetry tracing.

Documentation

This skill has comprehensive documentation:

When to Use

Use React Query for:

  • Fetching, caching, and synchronizing server state
  • API calls with automatic caching and revalidation
  • Data mutations with optimistic updates
  • Infinite scrolling or pagination
  • Background data refetching

Note: This codebase uses react-query v3.39.0, not the newer @tanstack/react-query v5. The API is different!

Core Principles

1. useQuery for Data Fetching

Use useQuery hook with query keys and custom useFetch integration.

Good:

// app/data-access/voucher/validate.ts:1
import { useQuery } from 'react-query';
import localFetch, { useFetch } from '@/libs/fetch';
import { useLocalizeParams } from '../utils';
import RequestIds from '../RequestIds';

export const useValidateVoucher: UseQuery<
  VoucherValidateResult,
  VoucherValidateParams
> = (params, options = {}) => {
  const { fetch } = useFetch();

  const localizedParams = {
    ...params,
    ...useLocalizeParams(),
  };

  const queryKey = [RequestIds['voucher.validate'], localizedParams];

  return useQuery<VoucherValidateResult>(
    queryKey,
    () => {
      return validateVoucher(localizedParams, queryKey, fetch);
    },
    options
  );
};

Why: Query keys enable automatic caching and invalidation. The useFetch hook provides OpenTelemetry tracing and error handling.

2. Structured Query Keys

Use arrays with RequestIds and params for consistent query keys.

Good:

// app/data-access/reactivation/subscription.ts:1
import { useQuery } from 'react-query';
import RequestIds from '../RequestIds';

const queryKey = [
  RequestIds['reactivation.subscription'],
  {
    subscriptionId,
    locale,
    systemCountry,
    ...otherParams
  }
];

return useQuery<SubscriptionResponse>(queryKey, fetchFunction, options);

Bad:

// Don't use string-only keys
const queryKey = 'subscription';

// Don't use unstructured keys
const queryKey = ['subscription', subscriptionId, locale];

Why: Structured keys with RequestIds ensure uniqueness and make cache invalidation easier. Including params in the key ensures cache correctness.

3. useMutation for Data Changes

Use useMutation for POST, PUT, DELETE operations.

Good:

// app/data-access/voucher-services/usePostDistributablesBenefits.ts:69
import { useMutation } from 'react-query';
import { useFetch } from '@/libs/fetch';

export const usePostDistributablesBenefitsMutation: UseMutation<
  PostDistributablesBenefitsResponse,
  PostDistributablesBenefitsParams
> = (options = {}) => {
  const { fetch } = useFetch();
  const localizedParams = useLocalizeParams();

  const mutationKey = [
    RequestIds['voucher-service.distributables.mutate.benefits'],
  ];

  return useMutation(
    (params) =>
      postDistributablesBenefits(
        { ...params, ...localizedParams },
        mutationKey,
        fetch
      ),
    options
  );
};

Why: Mutations handle side effects like creating/updating data and can trigger cache invalidations.

4. Integration with useFetch + OpenTelemetry

Always use custom useFetch hook for tracing and error handling.

Good:

// app/data-access/voucher-services/usePostDistributablesBenefits.ts:1
import localFetch, { useFetch } from '@/libs/fetch';

const postDistributablesBenefits: DataAccessPost<
  PostDistributablesBenefitsResponse,
  PostDistributablesBenefitsParams
> = async (params, queryKey, fetch = localFetch) => {
  const response = await fetch(
    `/voucher-service/distributables/${voucherCode}/benefits`,
    queryKey,
    {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      data: {
        customer_id: customerId,
        plan_id: planId,
      },
      query: {
        country: systemCountry,
        locale,
      },
      parentSpan,  // OpenTelemetry span for tracing
    }
  );

  return response.json();
};

export const useMutation = (options = {}) => {
  const { fetch } = useFetch();  // Get traced fetch instance

  return useMutation(
    (params) => postDistributablesBenefits(params, mutationKey, fetch),
    options
  );
};

Why: The useFetch hook adds OpenTelemetry tracing to all API calls, enabling performance monitoring and debugging.

5. TypeScript Generic Types

Use TypeScript generics for type-safe queries and mutations.

Good:

import { UseQueryOptions } from 'react-query';

export const getReactivateSubscriptions: DataAccessGet<
  SubscriptionResponse,
  GetReactivationSubscriptionsParams
> = async (params, queryKey, fetch = localFetch) => {
  const result = await fetch('/reactivation-subscription', queryKey, {
    method: 'GET',
    query: {
      country: params.systemCountry,
      locale: params.locale,
    },
  });

  return result.json();
};

Why: Type safety ensures params and responses are correct at compile time.

Query Options

Common Options

// Retry configuration
useQuery(queryKey, fetchFn, {
  retry: 3,  // Retry failed requests 3 times
});

// Stale time
useQuery(queryKey, fetchFn, {
  staleTime: 5 * 60 * 1000,  // 5 minutes
});

// Cache time
useQuery(queryKey, fetchFn, {
  cacheTime: 10 * 60 * 1000,  // 10 minutes
});

// Enabled/disabled
useQuery(queryKey, fetchFn, {
  enabled: !!subscriptionId,  // Only run when subscriptionId exists
});

// Callbacks
useQuery(queryKey, fetchFn, {
  onSuccess: (data) => {
    console.log('Data fetched successfully', data);
  },
  onError: (error) => {
    console.error('Fetch failed', error);
  },
});

Data Access Pattern

Standard Structure

// 1. Define types
type Params = { ... };
type Response = { ... };

// 2. Define fetch function
const fetchData: DataAccessGet<Response, Params> = async (
  params,
  queryKey,
  fetch = localFetch
) => {
  const result = await fetch('/endpoint', queryKey, {
    method: 'GET',
    query: params,
    parentSpan,
  });
  return result.json();
};

// 3. Define hook
export const useDataHook: UseQuery<Response, Params> = (
  params,
  options = {}
) => {
  const { fetch } = useFetch();
  const localizedParams = { ...params, ...useLocalizeParams() };
  const queryKey = [RequestIds['endpoint.name'], localizedParams];

  return useQuery<Response>(
    queryKey,
    () => fetchData(localizedParams, queryKey, fetch),
    options
  );
};

File Organization

data-access/
├── RequestIds.ts              # Centralized query key constants
├── schema.ts                  # Shared types
├── utils.ts                   # useLocalizeParams, etc.
├── reactivation/
│   ├── subscription.ts        # Query hooks
│   ├── subscription.test.ts   # Tests
│   └── schema.ts              # Types
└── voucher/
    ├── validate.ts            # Query hooks
    ├── validate.spec.ts       # Tests
    └── schema.ts              # Types

Common Mistakes

  1. Using TanStack Query v5 syntax - This codebase uses react-query v3, not v5!
  2. Forgetting useFetch integration - Always use useFetch for OpenTelemetry tracing
  3. String-only query keys - Use arrays with RequestIds and params
  4. Not including params in query key - Cache won't update when params change
  5. Importing from wrong package - Use 'react-query' NOT '@tanstack/react-query'

Quick Reference

v3 API (Current)

// Import from react-query (v3)
import { useQuery, useMutation } from 'react-query';

// useQuery
const { data, isLoading, error } = useQuery(queryKey, fetchFn, options);

// useMutation
const { mutate, isLoading } = useMutation(mutateFn, {
  onSuccess: (data) => { },
  onError: (error) => { },
});

// Call mutation
mutate({ param: 'value' });

With useFetch Integration

import { useFetch } from '@/libs/fetch';

const useSomeData = (params, options = {}) => {
  const { fetch } = useFetch();
  const queryKey = [RequestIds['some.data'], params];

  return useQuery(
    queryKey,
    () => fetchData(params, queryKey, fetch),
    options
  );
};

Testing

import { QueryClient, QueryClientProvider } from 'react-query';

const queryClient = new QueryClient({
  defaultOptions: {
    queries: { retry: false },
  },
});

render(
  <QueryClientProvider client={queryClient}>
    <Component />
  </QueryClientProvider>
);

Install

Download ZIP
Requires askill CLI v1.0+

AI Quality Score

88/100Analyzed 2/9/2026

An excellent, high-density technical guide for React Query v3. It provides clear patterns, version-specific warnings, and integrates well with internal observability tools. The structure is logical and highly actionable.

90
95
70
95
90

Metadata

Licenseunknown
Version-
Updated1/27/2026
Publisherguicheffer

Tags

apiobservabilitytesting