TanStack Query · API boundary
TanStack Query snake_case ↔ camelCase
TanStack Query should cache the shape your application actually uses. Normalize snake_case API responses inside shared query functions and convert mutation variables back to snake_case in the request function.
Return camelCase data from the query function
import { useQuery } from "@tanstack/react-query"
import { apiJson } from "./api"
type UserProfile = {
userId: number
firstName: string
}
export function useUserProfile(userId: number) {
return useQuery({
queryKey: ["user-profile", userId],
queryFn: () => apiJson<UserProfile>(`/users/${userId}`),
})
}When the query function returns normalized data, every component reading that query key sees the same camelCase shape.
Convert mutation variables at the request boundary
import { useMutation, useQueryClient } from "@tanstack/react-query"
import { apiJson } from "./api"
type UpdateUser = {
firstName: string
}
export function useUpdateUser(userId: number) {
const queryClient = useQueryClient()
return useMutation({
mutationFn: (input: UpdateUser) =>
apiJson(`/users/${userId}`, {
method: "PATCH",
body: input,
}),
onSuccess: (data) => {
queryClient.setQueryData(["user-profile", userId], data)
},
})
}Keep mutation inputs typed in the application's camelCase model, then map them to the server's snake_case JSON contract inside the mutation function.
Production considerations
- TanStack Query v5 uses the single options-object form for useQuery and useMutation.
- Cache one canonical shape. Mixing raw snake_case and normalized camelCase under the same query key makes updates harder to reason about.
- Normalize mutation responses before writing them into the cache.
- Case conversion is a transport concern; React components should consume already-normalized domain data.
Test the JSON boundary online
Use the browser tool to test nested objects, arrays, excluded keys, stop paths, semantic prefixes, collision handling, and a key-only diff before you copy the same convention into an application.
Drop a .json file here or choose one from your device
Files are read only in your browser. Maximum file size: 5 MB.
{
"userProfile": {
"firstName": "Ada",
"createdAt": "2026-09-12"
}
}Parsing, file reading, formatting, and key conversion run locally in your browser. JSON input is not sent to a conversion server.