Skip to main content

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 the CoreProvider component, located at src/infrastructure/providers/Core. This ensures a centralized and consistent setup for data fetching across the application.

The CoreProvider initializes a QueryClient with default options and provides it to the app using QueryClientProvider.

Implementation​

import { useState, type ReactNode } from 'react';
import { QueryClient, QueryClientProvider, QueryClientConfig } from '@tanstack/react-query';

const defaultQueryClientConfig: QueryClientConfig = {
defaultOptions: {
queries: {
refetchOnMount: false,
refetchOnWindowFocus: false,
refetchOnReconnect: false,
},
},
};

export type CoreProviderProps = {
children: ReactNode;
queryClientConfig?: QueryClientConfig;
};

export const CoreProvider = ({ children, queryClientConfig }: CoreProviderProps) => {
const [queryClient] = useState(
() => new QueryClient(queryClientConfig ?? defaultQueryClientConfig)
);

return (
<QueryClientProvider client={queryClient}>
{children}
</QueryClientProvider>
);
};

You can override the default QueryClient configuration by passing queryClientConfig to the CoreProvider. This flexibility ensures the data-fetching strategy adapts to specific use cases.

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.

Example: Query keys array​

const useUser = (userId: string) => useKrakenQuery({
queryKey: ['user', userId],
queryFn: fetchUser,
});

Example: Reusable query key generator​

export const generatePageQueryKey = ({ id, locale }: { id: string; locale: string }) => ['page-content', id, locale];

We use these query key generators in components and hooks to maintain consistency:

import { useKrakenQuery } from '@foundation/infrastructure/api';
import { fetchPageItem } from '../api/fetchPageItem';
import { generatePageQueryKey } from '../utils/queryKeys';

export const usePageItem = ({ id, locale }: { id: string; locale: string }) => {
return useKrakenQuery({
queryKey: generatePageQueryKey({ id, locale }),
queryFn: () => fetchPageItem({ id, locale }),
});
};

Selectors​

Selectors transform data before it is used in components. This keeps component logic clean and ensures that only the required data shape is passed down.

Example: Viewer accounts​

const useViewerAccounts = () => useKrakenQuery({
queryKey: ['viewer', 'accounts'],
queryFn: viewerAccountsQuery,
select: (data) => ({
accounts: data.viewer?.accounts,
}),
});

Prefetching in server-side props​

For server-side rendering, we don't use the wrapper hooks—we use React Query's prefetching capabilities directly. This allows us to fetch data on the server and hydrate it for the client, improving performance and providing a seamless user experience.

We use a withSharedPageProps helper, located at apps/foundation/src/infrastructure/utils/pageProps.ts, to handle props shared by every page. This helper is called on every new page to retrieve server-side props required for prefetching, keeping the fetching of common data consistent across the application.

Example: Prefetching in server-side props​

import { QueryClient } from '@tanstack/react-query';

export const getServerSideProps: GetServerSideProps = async (context) => {
const queryClient = new QueryClient();

return {
props: await withSharedPageProps({ queryClient, context }),
};
};