# Use Arkiv with React

In a React app that connects wallets through wagmi, read through an Arkiv public client and write through an Arkiv wallet client built from wagmi’s connection. Users connect once, with the connect button you already have. This guide uses a Next.js app with RainbowKit, which includes wagmi and TanStack Query. The same code works with ConnectKit or plain wagmi.

Before you start, you need:

- A React app with wagmi and TanStack Query set up, for example from RainbowKit’s Next.js template
- The SDK installed

```bash
    bun add @arkiv-network/sdk
    ```
  ```bash
    pnpm add @arkiv-network/sdk
    ```
  ```bash
    yarn add @arkiv-network/sdk
    ```
  ```bash
    npm install @arkiv-network/sdk
    ```
  ## Make the chains

`rabdi` makes the chain from your [access key](https://docs.arkiv.network/start-here/access-keys/). Arkiv’s public RPC is heavily rate limited to prevent abuse, so get a free key from the [Arkiv Hub](https://hub.arkiv.network/access-keys). Make the chain once and share it. Make a second one without the key for the user’s wallet:

```ts title="lib/arkiv.ts"
import { rabdi } from "@arkiv-network/sdk/chains"

export const chain = rabdi({
  accessKey: process.env.NEXT_PUBLIC_ARKIV_ACCESS_KEY ?? "",
})

// For the user's wallet, which keeps the chain's RPC URL
export const walletChain = rabdi({ accessKey: "" })
```

The key ships to the browser, so add your site to the key’s **Origins** on the Hub. See [lock the key down](https://docs.arkiv.network/start-here/access-keys/#lock-the-key-down).

## Add Rabdi to the wagmi config

Add `walletChain` to the chains your config lists, next to any chains your app already uses:

```ts title="lib/wagmi.ts"
import { getDefaultConfig } from "@rainbow-me/rainbowkit"
import { walletChain } from "@/lib/arkiv"

export const config = getDefaultConfig({
  appName: "My App",
  projectId: "your_walletconnect_project_id",
  chains: [walletChain],
  ssr: true,
})
```

RainbowKit then asks the user to switch networks when their wallet is on a chain that isn’t in the list. If the wallet doesn’t have Rabdi yet, wagmi adds it with the chain’s RPC URL. The wallet keeps that URL and uses it for every app on Rabdi, so `walletChain` has no key. Otherwise other apps would spend your quota. Your public client still reads with `chain` and your key.

## Share the schema and a public client

Reads don’t need the user’s wallet, so one public client serves the whole app. Put it in `lib/arkiv.ts`, next to the chain and your schemas:

```ts title="lib/arkiv.ts"
import { createPublicClient } from "@arkiv-network/sdk"
import { addr, bytes, dec, defineSchema, str } from "@arkiv-network/sdk/schema"
import { http } from "viem"

export const Posts = defineSchema("my-app.posts", {
  author: addr().indexed(),
  category: str().indexed(),
  score: dec().indexed(),
  title: str(),
  body: bytes(),
  summary: str().optional(),
})

export const publicClient = createPublicClient({
  chain,
  transport: http(),
})
```

<RpcTerms />

## Read with TanStack Query

Wrap each query in `useQuery`. This hook lists the posts an account owns:

```ts title="hooks/use-posts.ts"
import { useQuery } from "@tanstack/react-query"
import type { Address } from "viem"
import { Posts, publicClient } from "@/lib/arkiv"

export function usePosts(owner: Address | undefined) {
  return useQuery({
    queryKey: ["posts", owner],
    enabled: owner !== undefined,
    queryFn: async () => {
      const { entities } = await publicClient
        .select({ $key: true, title: true, score: true })
        .from(Posts)
        .ownedBy(owner as Address)
        .fetch()
      return entities
    },
  })
}
```

Anyone can write entities with your schema’s tag, so `ownedBy` keeps the list to the account’s own posts. Keep `bigint` values out of query keys, since TanStack Query can’t hash them.

Use the hook with the connected account:

```tsx title="components/my-posts.tsx"
"use client"

import { useAccount } from "wagmi"
import { usePosts } from "@/hooks/use-posts"

export function MyPosts() {
  const { address } = useAccount()
  const { data: posts, isPending } = usePosts(address)

  if (!address) return <p>Connect a wallet to see your posts.</p>
  if (isPending) return <p>Loading…</p>

  return (
    <ul>
      {posts?.map((post) => (
        <li key={post.$key}>
          {post.title.value} ({post.score.value})
        </li>
      ))}
    </ul>
  )
}
```

## Build a wallet client from wagmi

Writes need an Arkiv wallet client. Build it from the connection wagmi already manages:

```ts title="hooks/use-arkiv-wallet.ts"
import { createWalletClient } from "@arkiv-network/sdk"
import { useMemo } from "react"
import { custom } from "viem"
import { useWalletClient } from "wagmi"
import { chain } from "@/lib/arkiv"

export function useArkivWallet() {
  // Only resolves while the wallet is connected and on Rabdi
  const { data: walletClient } = useWalletClient({ chainId: chain.id })

  return useMemo(() => {
    if (!walletClient) return undefined
    return createWalletClient({
      chain,
      transport: custom(walletClient.transport),
      account: walletClient.account,
    })
  }, [walletClient])
}
```

`custom(walletClient.transport)` sends the Arkiv client’s requests through the connected wallet, so the user approves each write there. With `chainId: chain.id`, the hook returns `undefined` until the wallet is connected and on Rabdi.

Don’t use `window.ethereum` in a wagmi app. It may not be the wallet the user connected, and with WalletConnect there may be none.

## Write with a mutation

Wrap each write in `useMutation`, and invalidate the queries it changes:

```ts title="hooks/use-create-post.ts"
import { ExpirationTime } from "@arkiv-network/sdk"
import { addr, dec } from "@arkiv-network/sdk/schema"
import { useMutation, useQueryClient } from "@tanstack/react-query"
import { stringToBytes } from "viem"
import { useArkivWallet } from "@/hooks/use-arkiv-wallet"
import { Posts } from "@/lib/arkiv"

export function useCreatePost() {
  const arkiv = useArkivWallet()
  const queryClient = useQueryClient()

  return useMutation({
    mutationFn: async (post: { title: string; body: string }) => {
      if (!arkiv) throw new Error("Connect a wallet on Rabdi first")
      const values = {
        author: addr(arkiv.account.address),
        category: "news",
        score: dec("4.5"),
        title: post.title,
        body: stringToBytes(post.body),
      }
      const expiration = ExpirationTime.fromDays(30)
      return arkiv.createEntity(Posts, values, { expiration })
    },
    onSuccess: () => queryClient.invalidateQueries({ queryKey: ["posts"] }),
  })
}
```

`createEntity` resolves once the post is in a block, so the refetched list includes it. Call the mutation from a component:

```tsx title="components/new-post-button.tsx"
"use client"

import { useCreatePost } from "@/hooks/use-create-post"
import { useArkivWallet } from "@/hooks/use-arkiv-wallet"

export function NewPostButton() {
  const ready = useArkivWallet() !== undefined
  const createPost = useCreatePost()

  return (
    <button
      disabled={!ready || createPost.isPending}
      onClick={() => createPost.mutate({ title: "Hello", body: "From React" })}
    >
      {createPost.isPending ? "Confirm in your wallet…" : "New post"}
    </button>
  )
}
```

## Next.js

- A component that calls these hooks is a client component, so it needs `"use client"` at the top. The hooks don’t
- `lib/arkiv.ts` has no browser code. Server components and route handlers can import `publicClient` and query with it

## Next steps

- [Filter results](https://docs.arkiv.network/querying/filter/): narrow a query beyond the owner
- [Paginate results](https://docs.arkiv.network/querying/paginate/): load long lists page by page
- [Watch entity events](https://docs.arkiv.network/querying/watch-events/): update the UI when entities change