useMutate
stableReact 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.
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>RequiredThe 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) => voidCallback fired when the mutation succeeds. Receives the returned data and the arguments passed to mutate.
options.onError
(error, args) => voidCallback fired when the mutation fails. Receives the error and the arguments passed to mutate.
options.onSettled
(data, error, args) => voidCallback fired when the mutation completes (success or error). Receives data (or undefined), error (or undefined), and args.
options.queryKey
QueryKeyQuery 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:
Function to trigger the mutation. Errors are caught and stored in state.
Function to trigger the mutation and return a promise. Throws on error.
True while the mutation is in progress.
The data returned from the last successful mutation.
The error from the last failed mutation, undefined otherwise.
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 (<buttononClick={() => 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 (<buttononClick={() => 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 todowindow.location.href = `/todos/${newTodo.id}`;} catch (error) {// Error is also stored in stateconsole.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); }}><inputvalue={form.name}onChange={(e) => setForm(f => ({ ...f, name: e.target.value }))}placeholder="Name"/><inputvalue={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)