Skip to content

React Query - Axios Instance Injection via QueryClient Meta #2887

@FR073N

Description

@FR073N

Description

When using client: 'react-query' with httpClient: 'axios', developers need to inject a pre-configured axios instance (with interceptors, auth, base URL) at runtime. This is critical for:

  • NPM package distribution: Library consumers need to inject their own axios config
  • SSR: Each server request needs its own axios with request-specific headers
  • Testing: Easy mock injection without complex module mocking

The axios client already supports this via PR #2851 (factory pattern). This proposal adds similar support for react-query using React Query’s native meta feature.

Related to #2851

Current behavior

Option 1: Global axios import → No configuration possible

Option 2: Custom mutator → Singleton created at module load, cannot be injected by library consumers or configured per-request (SSR)

Proposed behavior

New option httpClientInjection: 'reactQueryMeta' that reads axios from QueryClient.defaultOptions.meta:

Config

// orval.config.ts
{
  output: {
    client: 'react-query',
    httpClient: 'axios',
    httpClientInjection: 'reactQueryMeta', // NEW
  }
}

Generated code

// Resilient helpers - fallback to default axios if not configured
const getQueryAxiosInstance = (queryClient: QueryClient): AxiosInstance => {
  try {
    const instance = queryClient.getDefaultOptions()?.queries?.meta?.axiosInstance;
    return (instance as AxiosInstance) ?? axios;
  } catch {
    return axios;
  }
};

const getMutationAxiosInstance = (queryClient: QueryClient): AxiosInstance => {
  try {
    const instance = queryClient.getDefaultOptions()?.mutations?.meta?.axiosInstance;
    return (instance as AxiosInstance) ?? axios;
  } catch {
    return axios;
  }
};

// Generated hooks use the helpers
export const useListPets = (params?: ListPetsParams) => {
  const queryClient = useQueryClient();
  const axiosInstance = getQueryAxiosInstance(queryClient);

  return useQuery({
    queryKey: getListPetsQueryKey(params),
    queryFn: ({ signal }) => axiosInstance.get('/pets', { params, signal }).then(r => r.data),
  });
};

export const useCreatePets = () => {
  const queryClient = useQueryClient();
  const axiosInstance = getMutationAxiosInstance(queryClient);

  return useMutation({
    mutationFn: (data) => axiosInstance.post('/pets', data).then(r => r.data),
  });
};

Consumer usage

// Configure axios with interceptors
const apiClient = axios.create({ baseURL: process.env.API_URL });
apiClient.interceptors.request.use(addAuthHeader);
apiClient.interceptors.response.use(handleTokenRefresh);

// Inject via QueryClient meta
const queryClient = new QueryClient({
  defaultOptions: {
    queries: { meta: { axiosInstance: apiClient } },
    mutations: { meta: { axiosInstance: apiClient } },
  },
});

// Hooks automatically use the injected axios
const { data } = useListPets(); // Uses apiClient !

Proposed solution

File Changes
packages/core/types.ts Add httpClientInjection?: 'none' | 'reactQueryMeta'
packages/query/src/* Generate helpers + useQueryClient() in hooks

Benefits

  • Zero memory overhead - No closures, reads from existing QueryClient
  • SSR native - One QueryClient per request with its own axios
  • Backwards compatible - Opt-in, 'none' is default
  • Resilient - Fallback to global axios if not configured or RQ v3

Compatibility

  • ✅ React Query v3 (fallback), v4+, v5+
  • ✅ All orval modes: single, split, tags, tags-split
  • ⚠️ Incompatible with custom mutator (mutually exclusive)

Metadata

Metadata

Assignees

Labels

axiosAxios related issue

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions