createMutation allows you create a Mutation Factory that works alongside Cerberus Queries. Mutations provide optimistic updates and invalidation options to keep your UI seamlessly "real-time" by fetching in the background on your behalf.
Mutations are used when you want to make updates to data that is associated with a query.
Likewise, if you have a query that will never have update-related actions associated with it - you don't need mutations.
Creating a mutation requires two things:
mutate is calledUltimately, when you create a mutation you are really just creating a factory that can be called where ever and whenever you are ready.
Depending on your goals will determine how to you the mutation. The following sections outline the different use cases whether you are optimistically updating or not.
Mutations can live at any level. All that matters is that the factory has access to the query.key to reference.
There are no restrictions to Cerberus Signal Primitives which means you can use them at any scope.
Optimistic updates allow you to update the state of your query data which which will render in the UI immediately while calling the Promise-based mutation in the background.
This is done by using the setQueryData helper function.
Utilizing this helper will allow your application to appearingly have "real-time" functionality.
Once the mutation is settled, the return value will automagically overwrite the optimistic value. If these are the same - there will be no visual difference in the UI. If there is a descrepency, the UI will automagically be corrected.
To use optimitic updates, add the onMutate method to the options Object of your second argument and call the setQueryData helper to update the state.
The setQueryData helper accepts a query.key with the same arguments that the original query expects. This is done by using the key property from the original query.
If you are interested in a legacy approach that matches traditional application updates via visual loading states - simply omit the onMutate option (and thus not using setQueryData).
onMutate does not create the optimistic update - setQueryData does. So, if you needed the onMutate lifecycle method you can still use it.
To use mutations within a component, pass the factory to the useMutation hook. This will return an Object that contains the mutate method and any of the other return properties.
When you trigger a mutation that includes onMutate and invalidate handlers, Cerberus orchestrates a complex, highly-optimized sequence of events under the hood.
Understanding this lifecycle is key to mastering Optimistic UI. Here is the exact chronology of what happens the millisecond you call mutate():
The user clicks a button and your component calls mutate(payload).
onMutate)Before the network request even begins, Cerberus synchronously executes your onMutate callback.
setQueryData to inject the expected result directly into the cache.version integer on that specific cache node.The asynchronous mutation function you defined in the factory (e.g., api.updateUser) is fired to the server. Your component can track this loading state using the isolated pending status.
invalidate)Once the server responds successfully, your invalidate callback fires.
When the background SWR fetch finally resolves, the engine performs one final, critical check before saving the data: The Version Lock.
mutate() again while the background fetch was running. Cerberus realizes this fetch is now stale, intercepts the data, and silently aborts the update. This guarantees your users will never experience a "Flash of Unstyled Content" (FOUC) or see their UI snap backward during rapid interactions.| Time | Action | UI State | Background Engine |
|---|---|---|---|
| 0ms | User calls mutate() | Instantly new | version: 1, Cache updated |
| 5ms | Network request starts | Instantly new | Mutation pending |
| 800ms | Network request succeeds | Instantly new | invalidation triggers SWR |
| 805ms | Background query starts | Instantly new | fetching database state... |
| 1800ms | Background query finishes | Stays consistent | Version check -> Cache synced |
createMutation accepts the following options:
| Params | Required | Description |
|---|---|---|
| mutator | true | The Promise-based function used when the mutate is called. |
| options | false | An Object of conditional options for defined mutations. |
The options Objecct contains the following optional properties:
| Property | Type | Description |
|---|---|---|
onMutate | (variables: V) => void | A callback to run when the mutation is triggered. |
onSuccess | (data: T, variables: V) => void | A callback to run when the mutation is successful. |
onError | (error: unknown, variables: V) => void | A callback to run when the mutation has errored. |
invalidate | 'all' | ((data: T, variables: V) => string[]) | A list of query.key to invalidate once the mutation has completed. |
useMutation returns a MutationReturn Object that contains the following properties:
| Index | Type | Description |
|---|---|---|
mutate | (variables: V) => Promise<T> | A function used to trigger the mutation. |
status | 'idle' | 'pending' | 'success' | 'error' | The Promise-based status of the mutation. |
error | unknown | undefined | The error thrown when something bad happens. |
data | T | undefined | The data returned from the mutation. |
setQueryDatasetQueryData doesn't return anything and accepts following properties:
| Index | Type | Description |
|---|---|---|
hashKey | string | A unique key provide from calling the query.key method with the expected arguments passed in provided from the onMutate parameters. |
updater | (prev: T | undefined) => T | A setter function that returns the query data state if existing and expects the new state returned. |
User b60013f2-981c-447d-971d-7ee4844e136f
User d69a9ff6-0c32-44ee-a30b-6e17bce903ae
User 74d97ee8-daea-4770-9e3d-d614826ee50c