> For the complete documentation index, see [llms.txt](https://docs.baas.sh/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.baas.sh/baas-sdk/integrations/react-query.md).

# TanStack Query recipe

Read and write on-chain data with TanStack Query on top of the BaaS SDK.

`baas.contract` calls are plain promises and fit any data library. With TanStack Query, the pattern is `useQuery` for reads, `useMutation` for writes, invalidate on success.

This recipe assumes the `baas` singleton from [Use with React](/baas-sdk/integrations/react.md), a passkey session (no `{ signer }` to pass) and Base (`8453`) selected with `await baas.chains.switch(8453)`. With an external wallet, pass the provider used at sign-in as `{ signer }`: see [Wallet operations](/baas-sdk/blockchain/wallet.md).

## Read

{% code title="hooks/useBalance.ts" %}

```ts
import { useQuery } from '@tanstack/react-query';
import type { UnitFormattedValue } from '@baas.sh/sdk';
import { baas } from '../baas';

export function useBalance(address: string) {
  return useQuery({
    queryKey: ['ERC20', 'balanceOf', 8453, address],
    queryFn: async () => {
      const [balance] = await baas
        .contract('ERC20').address('0x…')
        .function('balanceOf').params(address)
        .read<[UnitFormattedValue]>();
      return balance; // { raw, formatted, metadata }
    },
  });
}
```

{% endcode %}

Return the whole `UnitFormattedValue`. The component shows `data.formatted`, and a comparison uses `BigInt(data.raw)`; nothing in the hook converts. Put the selected chain in the `queryKey`: if your app allows network changes, update the key from `onChainChange` so a network switch refetches. Show the query's `error` state rather than a default value.

## Write

{% code title="hooks/useTransfer.ts" %}

```ts
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { SmartUnits } from '@baas.sh/sdk';
import { baas } from '../baas';

export function useTransfer() {
  const qc = useQueryClient();
  return useMutation({
    mutationFn: async ({ to, amount }: { to: string; amount: string }) => {
      const tx = await baas
        .contract('ERC20').address('0x…')
        .function('transfer').params(to, SmartUnits(amount)) // amount as typed: '1.5'
        .sendTransaction();
      return tx.wait(); // resolves once confirmed
    },
    onSuccess: () => qc.invalidateQueries({ queryKey: ['ERC20', 'balanceOf'] }),
  });
}
```

{% endcode %}

The amount stays a string from the input field to `SmartUnits()`: no `Number()`, no `parseUnits()`. BaaS scales it with the token's decimals, and refuses a value the token can't represent exactly. See [Smart units](/baas-sdk/blockchain/smart-units.md).

`tx.wait()` resolves once the transaction is confirmed, so `onSuccess` runs when the change is real: invalidate the affected reads there. To surface a revert before the user sees a prompt, simulate first, see [Preview before paying gas](/baas-sdk/blockchain/smart-contracts.md#preview-before-paying-gas).

## Next

* [Use with React](/baas-sdk/integrations/react.md) — the client and the session hook.
* [Smart contracts](/baas-sdk/blockchain/smart-contracts.md) — every verb of `baas.contract`.
* [Best practices](/baas-sdk/sdk.md#best-practices) — what the SDK covers, so your app never converts or calls a node itself.
