Skip to main content
AI Generated Content
πŸ€– This documentation was generated with AI assistance. Please report any issues you find.

React Query Hooks

Pikku generates fully typed TanStack React Query hooks from your function definitions. You get useQuery, useMutation, and useInfiniteQuery β€” with input/output types inferred from your functions. No manual type maintenance.

Setup​

1. Enable generation​

Add reactQueryFile to your pikku.config.json:

{
"clientFiles": {
"reactQueryFile": ".pikku/pikku-react-query.gen.ts"
}
}

2. Generate hooks​

npx pikku all
# or just the hooks:
npx pikku react-query

This generates a file with typed hooks that import from your generated RPC map.

3. Install dependencies​

The generated hooks need @tanstack/react-query and @pikku/react:

npm install @tanstack/react-query @pikku/react

4. Wire up the provider​

Wrap your app with both the React Query and Pikku providers:

import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { PikkuProvider, createPikku } from '@pikku/react'
import { PikkuFetch } from '.pikku/pikku-fetch.gen'
import { PikkuRPC } from '.pikku/pikku-rpc.gen'

const queryClient = new QueryClient()

const pikku = createPikku(PikkuFetch, PikkuRPC, {
serverUrl: 'https://api.example.com'
})

function App() {
return (
<QueryClientProvider client={queryClient}>
<PikkuProvider pikku={pikku}>
{/* your app */}
</PikkuProvider>
</QueryClientProvider>
)
}

Hooks​

usePikkuQuery​

For reading data. Wraps useQuery with type-safe RPC name and input:

import { usePikkuQuery } from '.pikku/pikku-react-query.gen'

function UserProfile({ userId }: { userId: string }) {
const { data, isLoading, error } = usePikkuQuery('getUser', { userId })

if (isLoading) return <p>Loading...</p>
if (error) return <p>Error: {error.message}</p>

// data is fully typed β€” matches your function's output type
return <h1>{data.name}</h1>
}

The query key is automatically set to [name, data], so queries with different inputs cache separately.

You can pass any standard useQuery options as the third argument:

usePikkuQuery('getUser', { userId }, { staleTime: 5000, refetchInterval: 10000 })

usePikkuMutation​

For writes. Wraps useMutation:

import { usePikkuMutation } from '.pikku/pikku-react-query.gen'

function CreateTodo() {
const { mutate, isPending } = usePikkuMutation('createTodo')

return (
<button
disabled={isPending}
onClick={() => mutate({ title: 'Buy milk' })}
>
Add Todo
</button>
)
}

The mutate callback is typed β€” it only accepts your function's input type and the result matches the output type.

usePikkuInfiniteQuery​

For paginated data. Only available for functions whose output includes nextCursor:

// This function qualifies for infinite query because its output has nextCursor
export const listItems = pikkuSessionlessFunc<
{ limit: number; nextCursor?: string },
{ items: string[]; nextCursor?: string }
>({
func: async (_services, data) => ({ items: [...], nextCursor: '...' }),
expose: true,
})
import { usePikkuInfiniteQuery } from '.pikku/pikku-react-query.gen'

function ItemList() {
const { data, fetchNextPage, hasNextPage } = usePikkuInfiniteQuery(
'listItems',
{ limit: 20 }
)

return (
<>
{data?.pages.flatMap(page => page.items).map(item => (
<div key={item}>{item}</div>
))}
{hasNextPage && (
<button onClick={() => fetchNextPage()}>Load more</button>
)}
</>
)
}

The cursor handling is wired up automatically β€” getNextPageParam and initialPageParam are set for you. You just pass the input data without nextCursor (it's managed by the infinite query).

If a function's output doesn't have nextCursor, TypeScript won't let you use it with usePikkuInfiniteQuery. That's intentional.

Workflow Hooks​

If your project uses Workflows, the generated file also includes:

  • useRunWorkflow(name) β€” mutation that runs a workflow to completion
  • useStartWorkflow(name) β€” mutation that starts a workflow and returns { runId }
  • useWorkflowStatus(name, runId) β€” query that polls workflow status by runId

Type Safety​

The generated hooks are type-checked at compile time. Invalid RPC names, wrong input shapes, and incorrect usage all produce TypeScript errors:

// βœ… Compiles β€” correct name and input
usePikkuQuery('getUser', { userId: '123' })

// ❌ TypeScript error β€” unknown RPC name
usePikkuQuery('doesNotExist', {})

// ❌ TypeScript error β€” wrong input type
usePikkuQuery('getUser', { wrong: 'field' })

// ❌ TypeScript error β€” output has no nextCursor
usePikkuInfiniteQuery('getUser', { userId: '123' })

Next Steps​

  • Exposed RPCs β€” How functions get exposed to clients
  • Fetch Client β€” Lower-level type-safe HTTP client
  • Workflows β€” Multi-step processes with React Query hooks