DocsBlog

Get Started

Components

Data Grid

Signals

Styling

Theming

↑↓Navigate
↵Select
EscClose
  • 1.8.0

  • Day

    Night

    Preview

    Switch mode
  • cerberus

    acheron

    elysium

    oceanus

Get Started
Components
Data Grid
Signals
Styling
Theming

Get started

OverviewReactivityData FetchingGlobal Stores

Primitives

Creating SignalsCreating QueriesCreating MutationsComputing Signal ValuesCreating EffectsContextual Signal StoresBatch UpdatesCleanup EffectsUntrack Signals

Hooks

Using QueriesUsing MutationsReading Primitive SignalsUsing SignalsUsing Store Instances

Components

Reactive Text

Creating Mutations

Learn how to update queries in React.

  • source
View as Markdown
Open this page in Markdown
Anthropic
Open in Claude
Ask questions about this page
OpenAI
Open in ChatGPT
Ask questions about this page
import { createMutation, useMutation } from '@cerberus/signals'

Usage

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 Mutations

Creating a mutation requires two things:

  1. A Promise to call when activated
  2. Mutation options to utilize when mutate is called

Ultimately, when you create a mutation you are really just creating a factory that can be called where ever and whenever you are ready.

Loading example...

Using Mutations

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.

Pattern 1: Optimistic Updates

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.

const updateUser = createMutation((payload: User) => api.updateUser(payload), {
// Optimistically update the UI instantly
onMutate: (vars) => {
  setQueryData<User>(getUser.key(vars.id), (prev) => {
    if (!prev) return { id: vars.id, name: vars.name }
    return { ...prev, name: vars.name }
  })
},
})

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.

Loading example...

Pattern 2: Non-optimistically

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.

Loading example...

Within Components

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.

const { mutate, ...state } = useMutation(myMutation)

The Mutation Lifecycle

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():

1. The Trigger

The user clicks a button and your component calls mutate(payload).

2. The Optimistic Update (onMutate)

Before the network request even begins, Cerberus synchronously executes your onMutate callback.

  • You use setQueryData to inject the expected result directly into the cache.
  • The Engine Lock: Cerberus instantly increments an internal version integer on that specific cache node.
  • The UI Update: The UI instantly snaps to the new state. Because this uses direct signal memory pointers, it executes in ~130 nanoseconds with zero main-thread blocking.

3. The Network Request

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.

4. Background SWR (invalidate)

Once the server responds successfully, your invalidate callback fires.

  • It flags the target query cache as "stale."
  • Because the UI already has data (your optimistic update), Cerberus intentionally bypasses React Suspense boundaries.
  • A background Stale-While-Revalidate (SWR) fetch is quietly triggered to verify the data against the database. The user experiences absolutely zero UI flickering.

5. Resolution & The Version Lock

When the background SWR fetch finally resolves, the engine performs one final, critical check before saving the data: The Version Lock.

  • It compares the cache's current version to the version it was when the fetch started.
  • If they match: The background data perfectly overwrites the cache, confirming the database is in sync with the UI.
  • If they differ: This means the user rapidly clicked 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.

Timeline Visualization

TimeActionUI StateBackground Engine
0msUser calls mutate()Instantly newversion: 1, Cache updated
5msNetwork request startsInstantly newMutation pending
800msNetwork request succeedsInstantly newinvalidation triggers SWR
805msBackground query startsInstantly newfetching database state...
1800msBackground query finishesStays consistentVersion check -> Cache synced

API

createMutation accepts the following options:

ParamsRequiredDescription
mutatortrueThe Promise-based function used when the mutate is called.
optionsfalseAn Object of conditional options for defined mutations.

Mutation Options

The options Objecct contains the following optional properties:

PropertyTypeDescription
onMutate(variables: V) => voidA callback to run when the mutation is triggered.
onSuccess(data: T, variables: V) => voidA callback to run when the mutation is successful.
onError(error: unknown, variables: V) => voidA 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.

Return

useMutation returns a MutationReturn Object that contains the following properties:

IndexTypeDescription
mutate(variables: V) => Promise<T>A function used to trigger the mutation.
status'idle' | 'pending' | 'success' | 'error'The Promise-based status of the mutation.
errorunknown | undefinedThe error thrown when something bad happens.
dataT | undefinedThe data returned from the mutation.

setQueryData

setQueryData doesn't return anything and accepts following properties:

IndexTypeDescription
hashKeystringA unique key provide from calling the query.key method with the expected arguments passed in provided from the onMutate parameters.
updater(prev: T | undefined) => TA setter function that returns the query data state if existing and expects the new state returned.

On this page

  • Usage
  • Creating Mutations
  • Using Mutations
    • Pattern 1: Optimistic Updates
    • Pattern 2: Non-optimistically
    • Within Components
  • The Mutation Lifecycle
    • 1. The Trigger
    • 2. The Optimistic Update (onMutate)
    • 3. The Network Request
    • 4. Background SWR (invalidate)
    • 5. Resolution & The Version Lock
    • Timeline Visualization
  • API
    • Mutation Options
    • Return
    • setQueryData
Edit this page on Github

User af74cfc1-5c9c-48ff-92d0-7f1297b9ceca

idle

User 871e7921-6896-44be-b067-a105828abf61

idle

User 25aacb11-9d47-4c60-9350-f725003af247

idle