React
Dependent React Queries Without Losing Tenant Context
By Vivek Jalondhara · 5 min read
Published
Coordinate tenant, project, and vendor requests with explicit query identities, dependency gates, and recovery states.
A price review screen can request perfectly valid data and still display the wrong result. A user changes projects while an earlier request is running; the vendor identifier stays selected; a cached response appears under the new project heading. Each request succeeds, but their relationship is wrong.
This educational scenario extends Sheet Parser’s vendor and project pricing domain with a hypothetical tenant boundary. It demonstrates a TanStack Query design, not the application’s deployed query architecture. The goal is to preserve context across dependent requests and rapid navigation.
Describe the resource before choosing the hook
Write down what makes the response unique: tenant, project, vendor, and any filter or page cursor. TanStack Query identifies cached results through query keys, so inputs that change the response belong in that identity. A vendor identifier alone cannot describe project-specific prices.
Treat the active scope as one navigation value. Changing the tenant should also clear project and vendor selections that belong to the previous scope. Otherwise, the interface can construct a new request from a mixture of old and new identifiers even when every cache key is technically distinct.
Gate the actual dependency
Suppose the project response contains the vendors linked to that project. The price query waits until that response is available and the chosen vendor appears in it. Both hooks still run unconditionally; the dependent query uses enabled to control execution.
The example also checks the condition inside its query function. This preserves the precondition if application code attempts a manual refetch. Membership in cached project data is a UI readiness check. The server must independently authorize the tenant, project, and vendor combination on every request.
Separate waiting from fetching
A disabled dependent query can be pending while its fetch status is idle. Showing a spinner for every pending result therefore creates a loading screen that never ends when no vendor is selected. Model the user-visible states directly: choose a vendor, loading project, loading prices, empty result, and failed request.
Keep the upstream error visible. If loading the project fails, retrying only the price query cannot fix the missing prerequisite. A background refresh failure deserves different treatment from a first-load failure because already displayed prices may still be useful when clearly marked as needing refresh.
Protect a scope change from old work
Pass the query function’s AbortSignal through the API client to its underlying request. TanStack Query can then cancel consumed signals when appropriate. Cancellation reduces unnecessary work; correctly scoped keys prevent an old response from becoming the new project’s cached result.
Avoid carrying previous data into a different tenant through a generic placeholder policy. Also reset local row selection and unfinished comparisons. Cache isolation does not automatically isolate React state, and a selected row identifier from the previous project can otherwise remain attached to an unrelated result.
Decide whether the waterfall is necessary
The project-to-prices sequence adds latency when prices truly need information returned by the project request. If the route already contains a valid project and vendor identity, an authorized price endpoint may accept both directly. That removes the dependency without moving permission checks into the browser.
Keep the sequential version when the first response determines the next resource or supplies a revision needed for consistency. After a price edit, invalidate the precise resource scope and any affected summary separately; broad invalidation can turn one edit into a burst of unrelated requests.
Exercise transitions, not only responses
Use fixtures where two tenants share the same project identifier. Switch scopes before a response arrives, select an unlinked vendor, fail the project request, and return an empty price list. Check the heading, rows, actions, and retry target together. A test that checks only the fetched array misses the context error that this design addresses.
Make vendor readiness explicit
The injected API client authenticates requests, forwards the signal, and validates response JSON. Call this hook inside an existing QueryClientProvider with a complete active tenant and project scope.
Illustrative example · TypeScript · Simplified for explanation
import { useQuery } from "@tanstack/react-query";
type Scope = { tenantId: string; projectId: string };
type Api = {
project(scope: Scope, signal: AbortSignal): Promise<{ vendorIds: string[] }>;
prices(scope: Scope, vendorId: string, signal: AbortSignal): Promise<unknown[]>;
};
function useVendorPrices(scope: Scope, vendorId: string | null, api: Api) {
const project = useQuery({
queryKey: ["project", scope.tenantId, scope.projectId],
queryFn: ({ signal }) => api.project(scope, signal),
});
const ready = vendorId !== null &&
project.data?.vendorIds.includes(vendorId) === true;
const prices = useQuery({
queryKey: ["prices", scope.tenantId, scope.projectId, vendorId],
enabled: ready,
queryFn: ({ signal }) => {
if (!ready || vendorId === null) throw new Error("Choose a linked vendor");
return api.prices(scope, vendorId, signal);
},
});
return { project, prices, ready };
}- Select tenant and project
- Load linked vendor identities
- Enable the scoped price request
- Render the matching result or recovery action
Conceptual flow for the example above.
Tradeoff to keep in mind
A dependency gate improves sequencing but adds a request waterfall. Flatten it only when the server can resolve and authorize the complete resource directly.