Mutations
Overview
Mutations in Zustand Q handle state updates such as adding, updating, or deleting data, with built-in support for asynchronous operations. They integrate seamlessly with Zustand's store, providing a straightforward way to manage side effects and state changes.
Configuration
Define mutations in the mutations
object of createStore
. Each mutation is configured with the following properties:
mutations: {
addItem: {
mutationFn: (variables: TVariables) => Promise<TData>,
onStore: (data: TData, set: SetState<TState>) => void,
onStart?: () => void,
onSuccess?: (data: TData) => void,
onError?: (error: unknown) => void,
onFinish?: (data?: TData, error?: unknown) => void,
},
},
mutationFn
: An async function that performs the mutation, acceptingvariables
(typeTVariables
) and returning a promise ofTData
.onStore
: A function to update the store with the mutation result, using Zustand'sset
method.- Lifecycle Hooks: Optional callbacks (
onStart
,onSuccess
,onError
,onFinish
) for handling mutation lifecycle events.
Note:
TVariables
andTData
are generic types you define based on your mutation's input and output.
Usage
Call a mutation from your store with optional configuration overrides to customize its behavior:
const { mutate: addItem, isPending } = useMyStore().addItem({
onStart: () => console.log("Starting mutation..."),
onSuccess: (data) => console.log("Added:", data),
onError: (error) => console.error("Error:", error),
onFinish: (data, error) => console.log("Finished with:", data || error),
});
// Trigger the mutation with variables
addItem({ name: "New Item" });
Optional Configuration Overrides
When calling a mutation, you can provide an object with the following optional lifecycle hooks:
onStart?: () => void
: Runs after theonStart
from the configuration when the mutation begins.onSuccess?: (data: TData) => void
: Runs after theonSuccess
from the configuration on successful mutation.onError?: (error: unknown) => void
: Runs after theonError
from the configuration if an error occurs.onFinish?: (data?: TData, error?: unknown) => void
: Runs after theonFinish
from the configuration when the mutation completes, receivingdata
(if successful) orerror
(if failed).
Note: These lifecycle hooks execute after their counterparts in the mutation configuration, allowing you to extend or customize behavior without altering the original setup.
Return Values
Mutations return an object with the following properties:
mutate
:(variables: TVariables) => Promise<TData>
- Function to trigger the mutation with specified variables.isPending
:boolean
-true
while the mutation is in progress.isSuccess
:boolean
-true
if the mutation completed successfully.isError
:boolean
-true
if the mutation failed.error
:unknown | null
- The error object if the mutation fails, ornull
if no error.successAt
:number | undefined
- Timestamp (in milliseconds) of the last successful mutation, orundefined
if not yet successful.errorAt
:number | undefined
- Timestamp (in milliseconds) of the last error, orundefined
if no error occurred.status
:"success" | "error" | "pending"
- Current state of the mutation.
Example
Here’s a practical example of adding a cat to a list:
import { createStore } from "zustand-q";
import axios from "axios";
interface Cat {
id: string;
name: string;
}
export const useCatStore = createStore({
initialData: { cats: [] as Cat[] },
mutations: {
addCat: {
mutationFn: async (variables: { name: string }) =>
await axios.post<{ data: Cat }>(
"https://api.example.com/cats",
variables
),
onStore: (data, set) =>
set((state) => ({ cats: [...state.cats, data.data] })),
onStart: () => console.log("Mutation started from config"),
},
},
});
const App = () => {
const { cats, addCat } = useCatStore();
const {
mutate: createCat,
isPending,
status,
} = addCat({
onStart: () => console.log("Mutation started from override"),
onSuccess: (data) => console.log("Cat added:", data.data),
});
return (
<div>
<button onClick={() => createCat({ name: "Mimi" })} disabled={isPending}>
{isPending ? "Adding..." : "Add Cat"}
</button>
<p>Status: {status}</p>
<ul>
{cats.map((cat) => (
<li key={cat.id}>
{cat.name} (ID: {cat.id})
</li>
))}
</ul>
</div>
);
};
In this example:
- The
addCat
mutation sends a POST request to add a new cat and updates thecats
array in the store. - The
onStart
from the configuration runs first, followed by theonStart
from the override. - The component displays the mutation status and the updated cat list.