Skip to content

Instantly share code, notes, and snippets.

@lolmaus
Created October 5, 2025 06:30
Show Gist options
  • Save lolmaus/d2a5ab2c9d9eb16a6187162047479fc9 to your computer and use it in GitHub Desktop.
Save lolmaus/d2a5ab2c9d9eb16a6187162047479fc9 to your computer and use it in GitHub Desktop.
useDerivedQuery — using TanStack Query as a state machine for derived state from another query
export const USE_JUNCTIONS_QUERY_KEY = 'data/junction/fetchJunctions' as const;
export const USE_JUNCTIONS_STALE_TIME = ONE_DAY;
export function useJunctions(): UseQueryResult<TJunctionFeature[], TAppError> {
return useQueryUnwrapResult<TPaginatedPayload<TJunction>, TJunctionFeature[]>(
{
queryKey: [USE_JUNCTIONS_QUERY_KEY],
staleTime: USE_JUNCTIONS_STALE_TIME,
gcTime: USE_JUNCTIONS_STALE_TIME,
queryFnResult: ({ signal }) => fetchJunctions({ signal }),
select(data: TPaginatedPayload<TJunction>): TJunctionFeature[] {
return data.data.map(junctionToJunctionFeature);
},
}
);
}
export function useJunctionsById(): UseQueryResult<
Record<number, TJunctionFeature> | undefined,
TAppError
> {
return useDerivedQuery({
useQueryResult: useJunctions(),
select: (junctions) =>
junctions && keyBy(junctions, (j) => j.properties.id),
queryKey: [USE_JUNCTIONS_QUERY_KEY, 'by id'],
staleTime: USE_JUNCTIONS_STALE_TIME,
});
}
export function useJunction(
junctionId: number
): UseQueryResult<TJunctionFeature | undefined, TAppError> {
return useDerivedQuery({
useQueryResult: useJunctionsById(),
select: (junctionsById) => junctionsById?.[junctionId],
queryKey: [USE_JUNCTIONS_QUERY_KEY, 'by id', junctionId],
staleTime: USE_JUNCTIONS_STALE_TIME,
});
}
import type { TAppError } from '@/utils/errors';
import {
useQuery,
type RefetchOptions,
type UseQueryResult,
} from '@tanstack/react-query';
export function useDerivedQuery<TParentData, TDerivedData>({
useQueryResult: useQueryResultParent,
queryKey,
select,
staleTime,
}: {
useQueryResult: UseQueryResult<TParentData, TAppError>;
queryKey: unknown[];
select: (data: TParentData | undefined) => TDerivedData | undefined;
staleTime: number;
}): UseQueryResult<TDerivedData | undefined, TAppError> {
const useQueryResultDerived = useQuery<null, never, TDerivedData | undefined>(
{
queryKey,
staleTime,
gcTime: staleTime,
queryFn: () => null,
select: () => {
return select(useQueryResultParent.data);
},
enabled: useQueryResultParent.isSuccess,
}
);
const { data: derivedData } = useQueryResultDerived;
const refetch: UseQueryResult<
TDerivedData | undefined,
TAppError
>['refetch'] = async (options?: RefetchOptions) => {
await useQueryResultParent.refetch(options);
return useQueryResultDerived;
};
const promise = useQueryResultParent.promise.then((data) => select(data));
if (useQueryResultParent.isPending) {
return {
...useQueryResultParent,
data: undefined,
refetch,
promise,
};
}
if (useQueryResultParent.isError) {
return {
...useQueryResultParent,
data: undefined,
refetch,
promise,
};
}
return {
...useQueryResultParent,
data: derivedData,
refetch,
promise: Promise.resolve(derivedData),
};
}
@lolmaus
Copy link
Author

lolmaus commented Oct 5, 2025

I'm not saying it's a great idea, but it's an idea...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment