mutation()

mutation() is query()'s write half: creating, updating, deleting.

You call mutate(input) to run it, and it tracks the loading and error states for you. No import.

source
<script>
const createPost = mutation('/api/posts', { method: 'POST' })

function create() {
  createPost.mutate({ title: 'New Post' })
}
</script>

<button onclick="create()" disabled={createPost.loading}>
  {createPost.loading ? 'Creating...' : 'Create Post'}
</button>

method defaults to POST. The other accepted values are PUT, PATCH and DELETE.

What you get back

Property Type What it is
mutate(input) (input) => Promise Run it
data T | undefined The last successful result
loading boolean In progress
error Error | undefined It failed
reset() () => void Clear data and error

Refreshing queries afterwards

List invalidates and those queries refetch as soon as the mutation succeeds:

source
<script>
const posts = query('posts', '/api/posts')

const deletePost = mutation('/api/posts', {
  method: 'DELETE',
  invalidates: ['posts']   // refetches the posts query on success
})
</script>

{posts.data && posts.data.map(post => (
  <div key={post.id}>
    {post.title}
    <button onclick={() => deletePost.mutate({ id: post.id })}>Delete</button>
  </div>
))}

invalidates matches the whole cache key, which is the query's name plus its serialised params. So invalidates: ['posts'] hits query('posts', '/api/posts') and nothing else - a query with params has a different key. Name the queries you intend to invalidate, and keep them param-free.

Callbacks

onSuccess and onError run after the request settles:

source
<script>
const updateProfile = mutation('/api/profile', {
  method: 'PUT',
  invalidates: ['profile'],
  onSuccess: () => ui.toast.show('Profile saved', { type: 'success' }),
  onError: (err) => ui.toast.show(err.message, { type: 'error' })
})
</script>

Sending it yourself

Pass a function instead of a URL for full control of the request:

source
<script>
const customMutation = mutation(async (data) => {
  const res = await fetch('/api/custom', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(data)
  })
  if (!res.ok) throw new Error('Failed')
  return res.json()
}, { invalidates: ['custom'] })
</script>

Want instant feedback? Update your local state the moment the button is clicked, then let invalidates reconcile it with the server.