Project Structure
This guide shows you how to organize Query Cache Flow in your React application for maximum maintainability and scalability.
Recommended Structure
src/
├── queries/
│ ├── client.ts # QueryClient configuration
│ └── index.ts # Query Cache Flow implementation
├── features/
│ ├── accounts/
│ │ ├── queries/
│ │ │ ├── index.ts # accountsQueryGroup definition
│ │ │ └── hooks.ts # Wrapped query/mutation hooks
│ │ ├── components/
│ │ │ └── AccountsList.tsx
│ │ └── api/
│ │ └── accounts.ts # API client functions
│ ├── transactions/
│ │ ├── queries/
│ │ │ ├── index.ts # transactionsQueryGroup
│ │ │ └── hooks.ts
│ │ └── ...
│ └── ...
├── generated/ # KUBB-generated code (optional)
│ ├── hooks/
│ └── types/
└── App.tsx
Core Infrastructure
1. Query Client (src/queries/client.ts)
import { QueryClient } from '@tanstack/react-query';
const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 1000 * 60 * 5, // 5 minutes
retry: 1,
},
mutations: {
retry: 0,
},
},
});
export default queryClient;
2. Query Cache Flow Implementation (src/queries/index.ts)
This file contains the complete Query Cache Flow implementation (see Installation for the full code).
Feature-Based Organization
Query Group Definition
Each feature should have a queries/index.ts file that defines its query groups:
// src/features/accounts/queries/index.ts
import { createQueryGroupCRUD } from 'src/queries';
export const accountsQueryGroup = createQueryGroupCRUD<string>('accounts');
For entities that don't need full CRUD:
// src/features/currencies/queries/index.ts
export const currenciesQueryGroup = {
list: {
queryKey: { entity: 'currencies', method: 'list' },
},
};
Wrapper Hooks
Create wrapper hooks in queries/hooks.ts:
// src/features/accounts/queries/hooks.ts
import { useGetAccounts as generatedUseAccounts } from 'src/generated/hooks';
import { accountsQueryGroup } from './index';
import { invalidateQueriesForKeys } from 'src/queries';
// Query hooks
export const useAccounts = () =>
generatedUseAccounts({
query: { queryKey: [accountsQueryGroup.list.queryKey] },
});
export const useAccount = (id: string) =>
generatedUseAccount(id, {
query: { queryKey: [accountsQueryGroup.detail.queryKey(id)] },
});
// Mutation hooks
export const useCreateAccount = () =>
useMutation({
mutationFn: createAccount,
onSuccess: (data) => {
invalidateQueriesForKeys([accountsQueryGroup.create.invalidates]);
accountsQueryGroup.create.normalize?.(data);
},
});
API Layer
Keep your API client functions separate from queries:
// src/features/accounts/api/accounts.ts
import axios from 'src/services/axios';
export interface Account {
id: string;
name: string;
balance: number;
}
export const fetchAccounts = async (): Promise<Account[]> => {
const { data } = await axios.get('/accounts');
return data;
};
export const fetchAccount = async (id: string): Promise<Account> => {
const { data } = await axios.get(`/accounts/${id}`);
return data;
};
export const createAccount = async (account: Omit<Account, 'id'>): Promise<Account> => {
const { data } = await axios.post('/accounts', account);
return data;
};