Skip to main content
Kweri supports two React usage patterns. If your backend has an OpenAPI spec, use path-based hooks with generated types. If you’re defining endpoints manually, use useQuery and useMutation directly.
After running kweri-gen, bind hooks to the generated EndpointByMethod map. You get fully-typed useGet, usePost, usePut, usePatch, and useDelete hooks without writing any endpoint definitions yourself.

Setup

// src/hooks/useKweri.ts
import { useSyncExternalStore } from 'react'
import { createReactPathHooks } from 'kweri'
import { EndpointByMethod } from 'kweri/generated'
import { kweri } from '@/lib/kweri'

export const { useGet, usePost, usePut, usePatch, useDelete } =
  createReactPathHooks(useSyncExternalStore, kweri, EndpointByMethod)

export { kweri }
Create the hook factory once and export it. Don’t call createReactPathHooks inside a component.

useGet

function useGet(
  path: string,
  params?: any,
  options?: ReactQueryOptions
): ReactQueryResult
import { useGet } from '@/hooks/useKweri'

function UserList() {
  const { data, isLoading, isError, error } = useGet('/users', {})

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

  return <ul>{data?.map(user => <li key={user.id}>{user.name}</li>)}</ul>
}

Mutation hooks

function usePost(path: string):   ReactMutationResult
function usePut(path: string):    ReactMutationResult
function usePatch(path: string):  ReactMutationResult
function useDelete(path: string): ReactMutationResult

Full CRUD example

import { useGet, usePost, useDelete, kweri } from '@/hooks/useKweri'

function UserManager() {
  const { data: users } = useGet('/users', {})
  const createMutation = usePost('/users')
  const deleteMutation = useDelete('/users/{id}')

  async function addUser(name: string) {
    await createMutation.mutateAsync({ body: { name } })
    kweri.invalidateByPath('/users')
  }

  async function removeUser(id: number) {
    await deleteMutation.mutateAsync({ path: { id: String(id) } })
    kweri.invalidateByPath('/users')
  }

  return (
    <ul>
      {users?.map(user => (
        <li key={user.id}>
          {user.name}
          <button onClick={() => removeUser(user.id)}>Delete</button>
        </li>
      ))}
    </ul>
  )
}
Path hooks resolve the endpoint schema at call time. If the path isn’t in the generated map, they throw [kweri] No endpoint registered for GET /unknown-path.

Without code generation

Use useQuery and useMutation when you’re defining endpoints manually with defineEndpoint.

Setup

// src/hooks/useKweri.ts
import { useSyncExternalStore } from 'react'
import { createReactQueryHooks } from 'kweri'
import { kweri } from '@/lib/kweri'

export const { useQuery, useMutation } =
  createReactQueryHooks(useSyncExternalStore, kweri)

export { kweri }
Create the hooks once and export them. Don’t call createReactQueryHooks inside a component.

useQuery

function useQuery<E extends Endpoint>(
  kweri: Kweri,
  endpoint: E,
  params: InferParams<E>,
  options?: ReactQueryOptions
): ReactQueryResult<InferResponse<E>, Error>

ReactQueryOptions

interface ReactQueryOptions {
  enabled?: boolean  // default: true — set false to skip the fetch
}

ReactQueryResult

interface ReactQueryResult<TData, TError> {
  data:       TData | undefined
  status:     'idle' | 'loading' | 'success' | 'error'
  error:      TError | undefined
  refetch:    () => Promise<void>
  isFetching: boolean   // true while a request is in flight
  isLoading:  boolean   // status === 'loading'
  isSuccess:  boolean   // status === 'success'
  isError:    boolean   // status === 'error'
}

Example

import { useQuery } from '@/hooks/useKweri'
import { getUsers } from '@/api/users'

function UserList() {
  const { data, isLoading, isError, error, refetch } = useQuery(getUsers, {})

  if (isLoading) return <p>Loading...</p>
  if (isError)   return <p>Error: {error?.message} <button onClick={refetch}>Retry</button></p>

  return (
    <ul>
      {data?.map(user => <li key={user.id}>{user.name}</li>)}
    </ul>
  )
}

Conditional fetching

function UserProfile({ userId }: { userId?: number }) {
  const { data } = useQuery(
    getUserById,
    { path: { id: userId ?? 0 } },
    { enabled: userId !== undefined }
  )

  return <div>{data?.name}</div>
}

useMutation

function useMutation<E extends Endpoint>(
  kweri: Kweri,
  endpoint: E
): ReactMutationResult<InferResponse<E>, Error>

ReactMutationResult

interface ReactMutationResult<TData, TError> {
  mutate:      (vars?: unknown) => void            // fire-and-forget
  mutateAsync: (vars?: unknown) => Promise<TData>  // awaitable
  status:      'idle' | 'loading' | 'success' | 'error'
  error:       TError | undefined
  reset:       () => void
  isLoading:   boolean
  isSuccess:   boolean
  isError:     boolean
}

Example

import { useMutation, kweri } from '@/hooks/useKweri'
import { createUser } from '@/api/users'

function CreateUserForm() {
  const { mutateAsync, isLoading, isError, error, reset } = useMutation(createUser)
  const [name, setName] = useState('')

  async function handleSubmit(e) {
    e.preventDefault()
    try {
      await mutateAsync({ body: { name, email: `${name}@example.com` } })
      kweri.invalidateByPath('/users')
    } catch {}
  }

  return (
    <form onSubmit={handleSubmit}>
      <input value={name} onChange={e => setName(e.target.value)} />
      <button type="submit" disabled={isLoading}>
        {isLoading ? 'Creating...' : 'Create User'}
      </button>
      {isError && <p onClick={reset}>{error?.message} (click to dismiss)</p>}
    </form>
  )
}