---
title: Data fetching
description: How server functions, TanStack Query, and hooks compose - the client-side data pattern applications use.
---

Data flows one way in Africa OS: server functions fetch or mutate, TanStack Query caches, hooks expose a clean API, and components render. This page documents the full client-side data pattern.

### The flow

```mermaid
sequenceDiagram
    participant Component
    participant Hook as useProducts
    participant Query as TanStack Query
    participant Fn as server function
    participant Runtime as request runtime
    participant Service as Effect service
    participant DB as PostgreSQL

    Component->>Hook: render
    Hook->>Query: useQuery
    Query->>Fn: listProductsFn({ data: { organizationSlug } })
    Fn->>Runtime: runRequestFolded(headers, program)
    Runtime->>Service: provide + run
    Service->>DB: scoped query
    DB-->>Service: rows
    Service-->>Runtime: typed Product[]
    Runtime-->>Fn: PublicProduct[]
    Fn-->>Query: cache
    Query-->>Hook: products
    Hook-->>Component: render
```

### Server functions as the data layer

Server functions are the only way components talk to the backend (see [TanStack Start](/frontend/tanstack-start)). They validate input, run an Effect program through the request runtime, and return serializable data or a folded error code.

### Query hooks

Hooks wrap server functions in TanStack Query, giving components a clean surface. The retail `useProducts` hook is the template (`packages/applications/retail/src/features/products/hooks/useProducts.ts`):

```ts
export function useProducts(scope?: ProductsScope) {
  const queryClient = useQueryClient();
  const organizationSlug = scope?.organizationSlug;

  const productsKey = ["retail", "products", organizationSlug ?? "active"];

  const { data: products, isLoading, error, refetch } = useQuery({
    queryKey: productsKey,
    queryFn: () => listProductsFn({ data: { organizationSlug } }),
    retry: false,
  });

  const createProduct = useMutation({
    mutationFn: (input) => createProductFn({ data: { ...input, organizationSlug } }),
    onSuccess: (result) => {
      if ("product" in result) {
        void queryClient.invalidateQueries({ queryKey: productsKey });
      }
    },
  });

  return { products: products ?? [], isLoading, error, refetch, createProduct };
}
```

### The conventions

**The organization slug is part of the query key.** `["retail", "products", organizationSlug ?? "active"]` - switching organizations refetches the correct catalog. Never build a query key without the tenant context.

**Mutations invalidate the list on success.** Each mutation checks the result shape (`"product" in result`) before invalidating, so only successful writes refresh the cache.

**Failures are folded, not thrown.** Server functions return `{ product } | { error }`. Hooks check the success branch and ignore error codes (or surface them) without a thrown exception.

**`retry: false` on reads.** Server data is already authenticated and validated; retrying a 500 from the server is usually pointless.

**Data defaults to a safe empty state.** `products: products ?? []` keeps components renderable while loading.

### The QueryClient

Both the shell and application packages export a `getQueryClient()` (`packages/applications/shell/src/queryClient.ts`):

- **Browser** - a stable, memoized client shared across the session.
- **Server (SSR)** - a fresh client per request, so cached data is never shared across users during server rendering.
- Defaults - `staleTime: 60_000`.

### The application route tree

Application packages export their internal route tree (`src/routes/tree.ts`) so hosts can compose routes with their own loaders, while the mounted application resolves the same pages from the URL. Both paths render identical pages.

### The rule

**Client components never query the database.** They call server functions, which run through the request runtime. Tenancy, authentication, and authorization stay server-side by construction.

### Next steps

- [Retail reference](/applications/retail-reference) - this pattern in full.
- [Adding a feature](/guides/adding-a-feature) - build a feature with this data flow.