Data fetching
Foundation uses graphql-request and TanStack Query (React Query) to call the Kraken API. React Query handles caching, background updates, and optimistic updates; graphql-request makes the network requests.
Kraken query and mutation hooks​
Foundation wraps React Query's hooks with custom logic for Kraken's authentication and error handling, implemented internally in @foundation/infrastructure/api. This keeps error handling and auth checks in one place, so components stay focused on rendering.
Why custom wrapper hooks?​
- Provide a unified interface for interacting with Kraken endpoints.
- Reduce repetitive code in components, keeping them focused on rendering logic.
- Handle complexities like masquerading sessions and conditional query execution.
useKrakenQuery​
Fetches data from Kraken. Wraps React Query's useQuery and adds custom logic for authentication and errors. See Creating a new query hook for a usage example.
useInfiniteKrakenQuery​
Fetches paginated data from Kraken. Wraps React Query's useInfiniteQuery and adds custom logic for authentication and errors.
useKrakenMutation​
Performs mutations against Kraken. Wraps React Query's useMutation and adds custom logic for error handling.
React Query setup​
React Query is configured in src/infrastructure/providers/Core. CoreProvider creates one QueryClient, provides it to the app, and hydrates server-prefetched state.
config and dehydratedState are required. The app passes both from pages/_app.tsx; queryClientConfig is optional.
Abridged implementation​
import type { AppConfig } from "@foundation/infrastructure/types/app";
import {
type DehydratedState,
HydrationBoundary,
QueryClient,
type QueryClientConfig,
QueryClientProvider,
} from "@tanstack/react-query";
import { type ReactNode, useState } from "react";
import { ConfigProvider } from "../Config";
const defaultQueryClientConfig: QueryClientConfig = {
defaultOptions: {
queries: {
refetchOnMount: false,
refetchOnReconnect: false,
refetchOnWindowFocus: false,
},
},
};
type CoreProviderProps = {
children: ReactNode;
config: AppConfig;
dehydratedState: DehydratedState;
queryClientConfig?: QueryClientConfig;
};
export const CoreProvider = ({
children,
config,
dehydratedState,
queryClientConfig,
}: CoreProviderProps) => {
const [queryClient] = useState(
() => new QueryClient(queryClientConfig ?? defaultQueryClientConfig)
);
return (
<QueryClientProvider client={queryClient}>
<HydrationBoundary state={dehydratedState}>
<ConfigProvider config={config}>
{/* Translated slugs, theme/UI, messages, auth, and Kraken API providers */}
{children}
</ConfigProvider>
</HydrationBoundary>
</QueryClientProvider>
);
};
This snippet omits provider nesting for readability. The current implementation also owns translated-slug, theme/UI, global-message, authentication, and Kraken API providers. See src/infrastructure/providers/Core/index.tsx for the complete composition. You can override the React Query defaults with queryClientConfig.
Query keys​
We use structured query keys to reflect the data hierarchy and ensure uniqueness. This makes it easier to manage caching, updates, and refetching behavior.
- Query keys are constructed as arrays that describe the data being fetched.
- Custom hooks are used to generate consistent query keys, reducing duplication and potential errors.
useKrakenQuery accepts a typed GraphQL document and variables; it builds the request internally. Do not pass a queryFn.
import { useKrakenQuery } from "@foundation/infrastructure/api";
import { getBillingAddress } from "../../../graphql/queries/getBillingAddress";
import { selectAccountBillingAddress } from "./selector";
type AccountBillingAddressProps = {
accountNumber: string;
};
export const generateAccountBillingAddressQueryKey = ({
accountNumber,
}: AccountBillingAddressProps) => ["account-billing-address", accountNumber];
export const useAccountBillingAddress = ({
accountNumber,
}: AccountBillingAddressProps) =>
useKrakenQuery({
document: getBillingAddress,
enabled: Boolean(accountNumber),
queryKey: generateAccountBillingAddressQueryKey({ accountNumber }),
select: selectAccountBillingAddress,
variables: { accountNumber },
});
Selectors​
The optional select function transforms the typed GraphQL result before React Query returns it. This keeps response-shape handling out of components.
For example, the hook above passes this selector:
import type { ResultOf } from "@foundation/gql-tada";
import type { DeepPartial } from "@foundation/infrastructure/types";
import type { getBillingAddress } from "../../../graphql/queries/getBillingAddress";
type BillingAddressQueryInput = DeepPartial<
ResultOf<typeof getBillingAddress>
>;
export const selectAccountBillingAddress = (input: BillingAddressQueryInput) => {
const account = input?.account;
if (!account) return null;
const address = (account.splitBillingAddress as string[] | null) ?? [];
return {
locality: address.slice(1).filter(Boolean).join(", "),
postalCode: account.billingAddressPostcode ?? "",
streetAddress: address[0] ?? "",
};
};
Prefetching in server-side props​
This helper is for the Next.js Pages Router only. Server-side props cannot call React hooks. Create a QueryClient, prefetch page-specific data directly when needed, then pass the client to withSharedPageProps.
withSharedPageProps, located at src/infrastructure/utils/pageProps.ts, prefetches the shared auth session, loads translations, and returns the dehydrated query state. It requires a props object even when the page has no additional props.
Example​
import { withSharedPageProps } from "@foundation/infrastructure/utils/pageProps";
import { QueryClient } from "@tanstack/react-query";
import type { GetServerSideProps } from "next";
export const getServerSideProps: GetServerSideProps = async (context) => {
const queryClient = new QueryClient();
return withSharedPageProps({
context,
props: {},
queryClient,
});
};