useApiMutation — React hook for mutations

A React hook over an ApiClient endpoint for writes (POST/PUT/DELETE/PATCH). Unlike useApiQuery, the request does not start automatically — you trigger it with mutate/mutateAsync. Mutations aren't cached (by REST method), and their invalidatesTags invalidate the cache — active useApiQuery hooks of neighbouring endpoints refetch on their own via the invalidation bus.

Import

typescript
import { useApiMutation } from 'synapse-storage/react'

Usage

typescript
const endpoints = pokemonApiClient.getEndpoints()

function CreatePokemon() {
  const { mutate, isLoading, isError, error } = useApiMutation(endpoints.createPokemon)

  return (
    <form
      onSubmit={(e) => {
        e.preventDefault()
        mutate({ name: 'Pikachu' }) // fire-and-forget
      }}
    >
      <button disabled={isLoading}>Create</button>
      {isError && <Error message={error?.message} />}
    </form>
  )
}

Return value

useApiMutation(endpoint, options?) returns:

FieldTypeDescription
mutate(params) => voidRun the mutation; errors are not thrown (read error/isError)
mutateAsync(params) => Promise<QueryResult>Run and await; rejects on error
dataTData | undefinedData of a successful response
errorError | undefinedMutation error
status'idle' | 'loading' | 'success' | 'error'Current status
isLoadingbooleanstatus === 'loading'
isErrorbooleanstatus === 'error'
isSuccessbooleanstatus === 'success'
reset() => voidReset state back to idle

options is QueryOptions (signal, headers, timeout, retry, …).

mutate vs mutateAsync

  • mutate(params) — fire-and-forget. The rejection is swallowed (state already reflects the error), so you don't need a .catch. Best for simple form submits.

  • mutateAsync(params) — returns the promise and rethrows on error, so you can await and branch in flow:

Invalidating related queries

Give the mutation endpoint invalidatesTags; any useApiQuery whose endpoint tags intersect will refetch automatically. No manual cache wiring required.

typescript
// endpoint config
createPokemon: create({
  request: (body) => ({ path: '/pokemon', method: 'POST', body }),
  invalidatesTags: ['PokemonList'],
})

// getList endpoint has tags: ['PokemonList']
// → after a successful createPokemon, an active useApiQuery(getList) refetches

Notes

  • StrictMode-safe. On unmount the in-flight request is aborted and state updates are skipped.

  • Mutations are never written to the cache (only GET is cached), so there's no fromCache here.

See also

  • useApiQuery — the companion hook for reads.

  • ApiClient — caching, tags and the invalidation bus.