Skip to main content

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, accepting variables (type TVariables) and returning a promise of TData.
  • onStore: A function to update the store with the mutation result, using Zustand's set method.
  • Lifecycle Hooks: Optional callbacks (onStart, onSuccess, onError, onFinish) for handling mutation lifecycle events.

Note: TVariables and TData 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 the onStart from the configuration when the mutation begins.
  • onSuccess?: (data: TData) => void: Runs after the onSuccess from the configuration on successful mutation.
  • onError?: (error: unknown) => void: Runs after the onError from the configuration if an error occurs.
  • onFinish?: (data?: TData, error?: unknown) => void: Runs after the onFinish from the configuration when the mutation completes, receiving data (if successful) or error (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, or null if no error.
  • successAt: number | undefined - Timestamp (in milliseconds) of the last successful mutation, or undefined if not yet successful.
  • errorAt: number | undefined - Timestamp (in milliseconds) of the last error, or undefined 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 the cats array in the store.
  • The onStart from the configuration runs first, followed by the onStart from the override.
  • The component displays the mutation status and the updated cat list.