references/query-patterns.md
3,196 tokens · o200k_base · 13,223 bytes
Source excerpt starting at line 1.# Query and Mutation Patterns ## Table of Contents - Intent- Directory structure- Query hook structure- Mutation hook structure- URL constants- Query keys- Type signatures- Anti-patterns ## Intent - Keep API hooks in `controllers/API/queries/{domain}/` organized by domain.- Use `UseRequestProcessor` for consistent behavior across all hooks.- Use the shared `api` Axios instance for all REST calls.- Type hooks with `useQueryFunctionType` or `useMutationFunctionType` from `types/api/`. ## Directory Structure ```textcontrollers/API/ api.tsx # Axios instance, interceptors, streaming helpers/ constants.ts # URL constants and getURL() services/ request-processor.ts # UseRequestProcessor hook queries/ flows/ use-get-flow.ts use-get-refresh-flows-query.ts use-post-add-flow.ts use-delete-delete-flows.ts use-get-download-flows.ts index.ts folders/ use-get-folder.ts use-get-folders.ts use-post-folders.ts use-patch-folders.ts use-delete-folders.ts index.ts variables/ use-get-global-variables.ts use-post-global-variables.ts use-patch-global-variables.ts index.ts auth/ use-get-autologin.ts use-post-login-user.ts use-post-logout.ts use-post-refresh-access.ts index.ts messages/ use-get-messages.ts use-get-messages-polling.ts use-delete-messages.ts index.ts``` ### Naming Convention - **Queries**: `use-get-{resource}.ts` (e.g., `use-get-flow.ts`, `use-get-global-variables.ts`)- **Create mutations**: `use-post-{resource}.ts` (e.g., `use-post-add-flow.ts`)- **Update mutations**: `use-patch-{resource}.ts` (e.g., `use-patch-global-variables.ts`)- **Delete mutations**: `use-delete-{resource}.ts` (e.g., `use-delete-messages.ts`)- **Other mutations**: `use-{action}-{resource}.ts` (e.g., `use-rename-session.ts`) ## Query Hook Structure Every query hook follows this structure: ```typescriptimport type { UseQueryResult } from "@tanstack/react-query"import type { useQueryFunctionType } from "@/types/api"import { api } from "../../api"import { getURL } from "../../helpers/constants"import { UseRequestProcessor } from "../../services/request-processor" // 1. Define the response type (or import from types/)interface GlobalVariable { id: string name: string value: string type: string} // 2. Export the hook typed with useQueryFunctionTypeexport const useGetGlobalVariables: useQueryFunctionType< undefined, GlobalVariable[]> = (options?) => { // 3. Get query helper from UseRequestProcessor const { query } = UseRequestProcessor() // 4. Define the query function using the api Axios instance const getGlobalVariablesFn = async (): Promise<GlobalVariable[]> => { const res = await api.get(`${getURL("VARIABLES")}/`) return res.data } // 5. Return the query result const queryResult: UseQueryResult<GlobalVariable[], Error> = query( ["useGetGlobalVariables"], getGlobalVariablesFn, { refetchOnWindowFocus: false, ...options, }, ) return queryResult}``` ### Query Hook with Parameters When a query takes parameters, use the first type argument: ```typescriptexport const useGetFolder: useQueryFunctionType< { id: string }, FolderResponse> = (params, options?) => { const { query } = UseRequestProcessor() const getFolderFn = async (): Promise<FolderResponse> => { const res = await api.get(`${getURL("FOLDERS")}/${params.id}`) return res.data } return query(["useGetFolder", params.id], getFolderFn, { ...options, })}``` ### Important: Some "get" Hooks Use Mutation In the Langflow codebase, some hooks named `use-get-*` (e.g., `useGetFlow`) are actually **mutation hooks** typed with `useMutationFunctionType`. This happens when the "get" operation is triggered imperatively (on demand) rather than declaratively (on mount/re-render). Check the actual type signature before following the query pattern — if a hook uses `mutate` from `UseRequestProcessor`, follow the Mutation Hook Structure below instead. ### Query Hook with Store Updates Many Langflow queries update Zustand stores as a side effect within the query function: ```typescriptexport const useGetGlobalVariables: useQueryFunctionType< undefined, GlobalVariable[]> = (options?) => { const { query } = UseRequestProcessor() // Access store setters const setGlobalVariablesEntries = useGlobalVariablesStore( (state) => state.setGlobalVariablesEntries, ) const getGlobalVariablesFn = async (): Promise<GlobalVariable[]> => { const res = await api.get(`${getURL("VARIABLES")}/`) // Update store as side effect of fetching setGlobalVariablesEntries(res.data.map((entry) => entry.name)) return res.data } return query(["useGetGlobalVariables"], getGlobalVariablesFn, { refetchOnWindowFocus: false, ...options, })}``` ## Mutation Hook Structure Every mutation hook follows this structure: ```typescriptimport type { UseMutationResult } from "@tanstack/react-query"import type { useMutationFunctionType } from "@/types/api"import { api } from "../../api"import { getURL } from "../../helpers/constants"import { UseRequestProcessor } from "../../services/request-processor" // 1. Define the payload typeinterface PostAddFlowPayload { name: string data: ReactFlowJsonObject description: string folder_id: string} // 2. Export the hook typed with useMutationFunctionTypeexport const usePostAddFlow: useMutationFunctionType< undefined, // Params (undefined if none) PostAddFlowPayload // Variables (mutation payload)> = (options?) => { // 3. Get mutate helper and queryClient from UseRequestProcessor const { mutate, queryClient } = UseRequestProcessor() // 4. Define the mutation function using the api Axios instance const postAddFlowFn = async (payload: PostAddFlowPayload): Promise<any> => { const response = await api.post(`${getURL("FLOWS")}/`, { name: payload.name, data: payload.data, description: payload.description, folder_id: payload.folder_id || null, }) return response.data } // 5. Return the mutation with cache invalidation in onSettled // NOTE: Place hook-specific options (onSettled, retry) BEFORE ...options // so consumers can override them if needed. This matches the codebase convention. const mutation: UseMutationResult<any, any, PostAddFlowPayload> = mutate( ["usePostAddFlow"], postAddFlowFn, { onSettled: (response) => { if (response) { // Invalidate related queries so they refetch queryClient.refetchQueries({ queryKey: ["useGetRefreshFlowsQuery", { get_all: true, header_flows: true }], }) queryClient.refetchQueries({ queryKey: ["useGetFolder", response.folder_id], }) } }, ...options, // Consumer options come LAST (can override onSettled, retry, etc.) }, ) return mutation}``` ### Delete Mutation Example ```typescriptexport const useDeleteMessages: useMutationFunctionType<undefined, string[]> = ( options?,) => { const { mutate, queryClient } = UseRequestProcessor() const deleteMessagesFn = async (messageIds: string[]): Promise<void> => { await api.delete(`${getURL("MESSAGES")}/`, { data: messageIds, }) } const mutation = mutate(["useDeleteMessages"], deleteMessagesFn, { onSettled: () => { queryClient.invalidateQueries({ queryKey: ["useGetMessages"] }) }, ...options, }) return mutation}``` ### Patch Mutation Example ```typescriptexport const usePatchGlobalVariables: useMutationFunctionType< undefined, { id: string; name: string; value: string; type: string }> = (options?) => { const { mutate, queryClient } = UseRequestProcessor() const patchGlobalVariablesFn = async (payload: { id: string name: string value: string type: string }): Promise<GlobalVariable> => { const response = await api.patch( `${getURL("VARIABLES")}/${payload.id}`, payload, ) return response.data } const mutation = mutate(["usePatchGlobalVariables"], patchGlobalVariablesFn, { onSettled: () => { queryClient.invalidateQueries({ queryKey: ["useGetGlobalVariables"] }) }, retry: false, ...options, }) return mutation}``` ## URL Constants All API paths are defined in `controllers/API/helpers/constants.ts`: ```typescriptexport const URLs = { FLOWS: "flows", FOLDERS: "projects", VARIABLES: "variables", MESSAGES: "monitor/messages", BUILDS: "monitor/builds", API_KEY: "api_key", FILES: "files", // ...} as const``` Use `getURL()` to construct full API paths: ```typescriptimport { getURL } from "../../helpers/constants" // Basic: /api/v1/flowsconst url = getURL("FLOWS") // With params: /api/v1/flows/{flowId}const url = `${getURL("FLOWS")}/${flowId}` // With v2 flag: /api/v2/flowsconst url = getURL("FLOWS", {}, true)``` When adding new endpoints, add the constant to `URLs` first, then use `getURL()` in the hook. ## Query Keys Query keys are arrays that uniquely identify cached data. Langflow conventions: ```typescript// Hook name as key (no params)["useGetGlobalVariables"] // Hook name + params for cache isolation["useGetFlow", flowId]["useGetFolder", folderId] // Hook name + object params for complex queries["useGetRefreshFlowsQuery", { get_all: true, header_flows: true }]["useGetMessages", { flowId, sessionId, page }]``` Rules:- The first element is always the hook name as a string.- Additional elements are parameters that differentiate cache entries.- Use objects for multi-parameter queries.- Keep keys consistent so invalidation works correctly. ## Type Signatures ### useQueryFunctionType Used for query hooks. Defined in `types/api/index.ts`: ```typescript// Without params: useQueryFunctionType<undefined, ResponseType>// The hook signature becomes: (options?) => UseQueryResult<ResponseType> // With params: useQueryFunctionType<ParamsType, ResponseType>// The hook signature becomes: (params, options?) => UseQueryResult<ResponseType>``` ### useMutationFunctionType Used for mutation hooks: ```typescript// Without params: useMutationFunctionType<undefined, VariablesType>// The hook signature becomes: (options?) => UseMutationResult<Data, Error, Variables> // With params: useMutationFunctionType<ParamsType, VariablesType>// The hook signature becomes: (params, options?) => UseMutationResult<Data, Error, Variables>``` ## Anti-Patterns ### Do Not Bypass UseRequestProcessor ```typescript// Do not call useQuery/useMutation directlyconst result = useQuery({ queryKey: ["flows"], queryFn: () => api.get("/api/v1/flows"),}) // Use UseRequestProcessor for consistent retry and error handlingconst { query } = UseRequestProcessor()const result = query(["useGetFlows"], () => api.get(getURL("FLOWS")))``` ### Do Not Bypass the api Instance ```typescript// Do not use raw axios or fetch for REST callsconst result = await axios.get("/api/v1/flows")const result = await fetch("/api/v1/flows") // Use the shared api instance (has interceptors for auth, headers, error handling)const result = await api.get(`${getURL("FLOWS")}/`)``` ### Do Not Hardcode URLs ```typescript// Do not hardcode API pathsconst result = await api.get("/api/v1/variables/") // Use getURL() helperconst result = await api.get(`${getURL("VARIABLES")}/`)``` ### Do Not Duplicate Query Keys ```typescript// Do not use different key strings for the same queryquery(["getFlows"], fetchFn) // in hook Aquery(["useGetFlows"], fetchFn) // in hook Bquery(["flows-list"], fetchFn) // in hook C // Use one canonical key matching the hook namequery(["useGetFlows"], fetchFn)``` ### Do Not Invalidate from Components ```typescript// Do not put invalidation logic in componentsconst Component = () => { const queryClient = useQueryClient() const { mutate } = useDeleteFlow() const handleDelete = () => { mutate(flowId, { onSuccess: () => { // Avoid: invalidation knowledge leaks into component queryClient.invalidateQueries({ queryKey: ["useGetFlows"] }) queryClient.invalidateQueries({ queryKey: ["useGetFolder"] }) }, }) }} // Put invalidation in the mutation hook's onSettledexport const useDeleteFlow = (options?) => { const { mutate, queryClient } = UseRequestProcessor() return mutate(["useDeleteFlow"], deleteFlowFn, { onSettled: () => { queryClient.invalidateQueries({ queryKey: ["useGetFlows"] }) queryClient.invalidateQueries({ queryKey: ["useGetFolder"] }) }, ...options, // Consumer options come LAST })}``` ### Do Not Create Thin Wrapper Hooks ```typescript// Do not create hooks that just re-export an existing query hookconst useFlows = () => { return useGetRefreshFlowsQuery({ get_all: true })} // Import the existing hook directly where neededimport { useGetRefreshFlowsQuery } from "@/controllers/API/queries/flows"```
Referenced from SKILL.md
Source excerpt starting at line 42.1. Identify the change surface. - Read `references/query-patterns.md` for hook structure, naming, URL patterns, and query/mutation call-site shape. - Read `references/runtime-rules.md` for conditional queries, invalidation, error handling, and streaming.
Source excerpt starting at line 69.- Use `references/query-patterns.md` for hook structure, naming conventions, directory layout, and anti-patterns.- Use `references/runtime-rules.md` for conditional queries, invalidation, `mutate` versus `mutateAsync`, error handling, and streaming requests.