Documentation

useMutate

stable

React hook for handling mutations with automatic state management

A powerful hook for handling asynchronous mutations (create, update, delete operations) with built-in loading, error, and success states. Features automatic type inference, query invalidation, and lifecycle callbacks.

React HookTypeScript

Type Inference

useMutate automatically infers Data and Args types from your mutation function. No need to specify generics in most cases!

Query Invalidation

Use the queryKey option to automatically refetch related queries after mutation. This keeps your UI in sync without manual refetching.

Signature

function useMutate<Fn extends MutationFn, Err = Error>(
mutationFn: Fn,
options?: UseMutateOptions
): UseMutateResult

Parameters

mutationFn

(...args) => Promise<Data>Required

The async function that performs the mutation. Types are automatically inferred from this function.

options

UseMutateOptionsDefault: {}

Optional configuration object for callbacks and query invalidation.

options.onSuccess

(data, args) => void

Callback fired when the mutation succeeds. Receives the returned data and the arguments passed to mutate.

options.onError

(error, args) => void

Callback fired when the mutation fails. Receives the error and the arguments passed to mutate.

options.onSettled

(data, error, args) => void

Callback fired when the mutation completes (success or error). Receives data (or undefined), error (or undefined), and args.

options.queryKey

QueryKey

Query key to invalidate after mutation completes. Useful for refetching related data.

Returns

Returns

UseMutateResult<Data, Err, Args>

Object containing mutation state and trigger functions

Properties:
mutate(...args) => void

Function to trigger the mutation. Errors are caught and stored in state.

mutateAsync(...args) => Promise<Data>

Function to trigger the mutation and return a promise. Throws on error.

isPendingboolean

True while the mutation is in progress.

dataData | undefined

The data returned from the last successful mutation.

errorErr | undefined

The error from the last failed mutation, undefined otherwise.

reset() => void

Resets the mutation state (isPending, data, error) to initial values.

Examples

Simple Mutation

Basic usage with just a mutation function

import { useMutate } from 'qortex-react';
function CreateTodo() {
const { mutate, isPending } = useMutate(
async (title: string) => {
const res = await fetch('/api/todos', {
method: 'POST',
body: JSON.stringify({ title }),
});
return res.json();
}
);
return (
<button
onClick={() => mutate('New Todo')}
disabled={isPending}
>
{isPending ? 'Creating...' : 'Create Todo'}
</button>
);
}

With Callbacks & Query Invalidation

Using callbacks and automatic query invalidation

import { useMutate } from 'qortex-react';
import { toast } from 'sonner';
function DeleteTodo({ todoId }: { todoId: string }) {
const { mutate, isPending } = useMutate(
async (id: string) => {
await fetch(`/api/todos/${id}`, { method: 'DELETE' });
},
{
onSuccess: () => toast.success('Todo deleted!'),
onError: (error) => toast.error(error.message),
queryKey: ['todos'], // Refetch todos list after deletion
}
);
return (
<button
onClick={() => mutate(todoId)}
disabled={isPending}
>
{isPending ? 'Deleting...' : 'Delete'}
</button>
);
}

Multiple Arguments

Mutation function with multiple arguments - all types are inferred

import { useMutate } from 'qortex-react';
function UpdateTodo({ todoId }: { todoId: string }) {
const { mutate, isPending, error } = useMutate(
async (id: string, title: string, completed: boolean) => {
const res = await fetch(`/api/todos/${id}`, {
method: 'PATCH',
body: JSON.stringify({ title, completed }),
});
return res.json();
},
{
onSuccess: (data, [id, title]) => {
console.log(`Updated todo ${id}: ${title}`);
},
}
);
return (
<div>
<button onClick={() => mutate(todoId, 'Updated', true)}>
Mark Complete
</button>
{error && <p>Error: {error.message}</p>}
</div>
);
}

Using mutateAsync

Using the promise-based mutateAsync for sequential operations

import { useMutate } from 'qortex-react';
function CreateAndNavigate() {
const { mutateAsync, isPending } = useMutate(
async (data: { title: string }) => {
const res = await fetch('/api/todos', {
method: 'POST',
body: JSON.stringify(data),
});
return res.json() as Promise<{ id: string }>;
}
);
const handleCreate = async () => {
try {
const newTodo = await mutateAsync({ title: 'New Todo' });
// Navigate to the new todo
window.location.href = `/todos/${newTodo.id}`;
} catch (error) {
// Error is also stored in state
console.error('Failed to create:', error);
}
};
return (
<button onClick={handleCreate} disabled={isPending}>
Create & View
</button>
);
}

Form Submission

Complete form example with reset functionality

import { useMutate } from 'qortex-react';
import { useState } from 'react';
interface FormData {
name: string;
email: string;
}
function ContactForm() {
const [form, setForm] = useState<FormData>({ name: '', email: '' });
const { mutate, isPending, error, data, reset } = useMutate(
async (formData: FormData) => {
const res = await fetch('/api/contact', {
method: 'POST',
body: JSON.stringify(formData),
});
if (!res.ok) throw new Error('Failed to submit');
return res.json();
},
{
onSuccess: () => {
setForm({ name: '', email: '' }); // Clear form
},
}
);
if (data) {
return (
<div>
<p>Thanks for your message!</p>
<button onClick={reset}>Send Another</button>
</div>
);
}
return (
<form onSubmit={(e) => { e.preventDefault(); mutate(form); }}>
<input
value={form.name}
onChange={(e) => setForm(f => ({ ...f, name: e.target.value }))}
placeholder="Name"
/>
<input
value={form.email}
onChange={(e) => setForm(f => ({ ...f, email: e.target.value }))}
placeholder="Email"
/>
{error && <p className="error">{error.message}</p>}
<button type="submit" disabled={isPending}>
{isPending ? 'Sending...' : 'Send'}
</button>
</form>
);
}

Behavior

Automatic Type Inference

Data and argument types are automatically inferred from the mutation function

Query Invalidation

Optionally invalidate and refetch queries after mutation completes

Lifecycle Callbacks

onSuccess, onError, and onSettled callbacks for handling mutation results

State Management

Built-in isPending, data, and error state with reset functionality

Best Practices

Do's

  • Use mutate() for fire-and-forget mutations, mutateAsync() when you need the result
  • Invalidate related queries using the queryKey option to keep data in sync
  • Handle errors in callbacks or by checking the error state
  • Use reset() to clear state before showing a form again
  • Leverage TypeScript - types are automatically inferred from mutationFn

Don'ts

  • Don't forget to handle loading states in your UI
  • Don't use useMutate for data fetching (use useQuery instead)
  • Don't ignore errors - always provide user feedback
  • Don't create new mutation functions on every render (define outside component or use useCallback)