DocsBlog
  • 1.3.0

  • light

    dark

    system

    Switch mode
  • Cerberus

    Acheron

    Elysium

Get Started
Components
Data Grid
Signals
Styling
Theming

Get Started

OverviewReactivityData FetchingStores

Primitives

createSignalcreateQueryNewcreateMutationNewcreateComputedcreateEffectcreateStoreContextbatchonCleanupNewuntrackNew

Hooks

useQueryNewuseMutationuseReaduseSignaluseStoreNew

Components

ReactiveText

On this page

Loading...

Loading...

Loading...

Loading...

Using Mutations

Learn how to use defined mutations in React.

  • source
import { useMutation } from '@cerberus/signals'

Usage

useMutation allows you to use a mutation factory (via createMutation) within the scope of a React component.

Mutations's allow you to update the state of a query and have built in support for Suspense and Error Boundaries with no extra effort.

When utilizing the invalidate option, a query will automagically update once the mutation has resolved. Combing this with onMutate/setQueryData will create optimistic updates in the UI.

Using Mutations

To use a mutation call the mutate method from the return Object.

Data

The data property contains the returned value of the mutation. This is not necessary for UI updates as mutations were designed to update querys on your behalf through invalidation.

Status

The status property is the lifecycle state associated with the mutation. To learn more about mutation lifecycles, see the create mutation docs.

API

useMutation accepts the following options:

ParamsRequiredDescription
MutationTupleYesThe value of the defined mutation factory.

Return

useMutation returns and Object with the following options:

PropertyTypeDescription
mutate(variables: V) => Promise<T>A function that calls the defined mutation Promise.
statusMutationStatusThe Promise-based status state of the mutation.
dataT | undefinedThe cached state of the query associated with it.
errorunknown | undefinedThe error thrown from the mutation.
idle
Copy
'use client'

import { Box, HStack, Stack } from '@/styled-system/jsx'
import { Button, Tag, Text } from '@cerberus/react'
import {
  createMutation,
  createQuery,
  setQueryData,
  useMutation,
  useQuery,
} from '@cerberus/signals'
import { Suspense } from 'react'

// 1. Define Query Factory
const getUser = createQuery(async (id: string) => {
  return await api.getUser(id)
}, 'queryGetUser')

// 2. Define Mutation Factory
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 }
    })
  },
  // Declarative cache invalidation using factory keys
  invalidate: (_data, vars) => [getUser.key(vars.id)],
})

// 3. Consume in Components seamlessly
function UserProfile(props: { id: User['id'] }) {
  const user = useQuery(getUser(props.id))
  return <Text>{user.name}</Text>
}

export function MutationDemo() {
  // Pretend this is from the URL or via props.id
  const id = crypto.randomUUID()

  const { mutate, status } = useMutation(updateUser)

  function handleUpdate() {
    mutate({ id, name: `User ${crypto.randomUUID()}` })
  }

  return (
    <HStack gap="lg" w="3/4">
      <Button onClick={handleUpdate}>Update Name</Button>

      <Stack>
        <Suspense fallback={<Box aria-busy h="44px" rounded="sm" w="366px" />}>
          <UserProfile id={id} />
        </Suspense>
        <Tag w="fit-content">{status}</Tag>
      </Stack>
    </HStack>
  )
}

// API

type User = { id: string; name: string }

const fakeDB = new Map<string, User>()
const delay = (ms: number) => new Promise((res) => setTimeout(res, ms))

const api = {
  getUser: async (id: User['id']) => {
    await delay(500)
    if (!fakeDB.has(id)) {
      fakeDB.set(id, { id, name: `User ${id}` })
    }
    return fakeDB.get(id)!
  },
  updateUser: async (payload: User) => {
    await delay(500)
    fakeDB.set(payload.id, { id: payload.id, name: payload.name })
    return payload
  },
}
idle
Copy
'use client'

import { Box, HStack, Stack } from '@/styled-system/jsx'
import { Button, Tag, Text } from '@cerberus/react'
import {
  createMutation,
  createQuery,
  setQueryData,
  useMutation,
  useQuery,
} from '@cerberus/signals'
import { Suspense } from 'react'

// 1. Define Query Factory
const getUser = createQuery(async (id: string) => {
  return await api.getUser(id)
}, 'queryGetUser')

// 2. Define Mutation Factory
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 }
    })
  },
  // Declarative cache invalidation using factory keys
  invalidate: (_data, vars) => [getUser.key(vars.id)],
})

// 3. Consume in Components seamlessly
function UserProfile(props: { id: User['id'] }) {
  const user = useQuery(getUser(props.id))
  return <Text>{user.name}</Text>
}

export function MutationDemo() {
  // Pretend this is from the URL or via props.id
  const id = crypto.randomUUID()

  const { mutate, status } = useMutation(updateUser)

  function handleUpdate() {
    mutate({ id, name: `User ${crypto.randomUUID()}` })
  }

  return (
    <HStack gap="lg" w="3/4">
      <Button onClick={handleUpdate}>Update Name</Button>

      <Stack>
        <Suspense fallback={<Box aria-busy h="44px" rounded="sm" w="366px" />}>
          <UserProfile id={id} />
        </Suspense>
        <Tag w="fit-content">{status}</Tag>
      </Stack>
    </HStack>
  )
}

// API

type User = { id: string; name: string }

const fakeDB = new Map<string, User>()
const delay = (ms: number) => new Promise((res) => setTimeout(res, ms))

const api = {
  getUser: async (id: User['id']) => {
    await delay(500)
    if (!fakeDB.has(id)) {
      fakeDB.set(id, { id, name: `User ${id}` })
    }
    return fakeDB.get(id)!
  },
  updateUser: async (payload: User) => {
    await delay(500)
    fakeDB.set(payload.id, { id: payload.id, name: payload.name })
    return payload
  },
}
{
  "id": "c11456d9-c056-4a94-8345-6fa6c1b49e0a",
  "name": "Cerby the Dawg"
}
Cerby Mutation Data:
Add Mutation Data:
Copy
'use client'

import { Box, Stack } from '@/styled-system/jsx'
import { Button, ButtonGroup, Text } from '@cerberus/react'
import {
  createMutation,
  setQueryData,
  useMutation,
  useQuery,
} from '@cerberus/signals'
import { Suspense } from 'react'
import { api, User } from './db'
import { queryUser } from './queries'

// Factories

const updateUser = createMutation(
  (payload: User) => api.updateUser(payload.id, payload.name),
  {
    onMutate: (vars) => {
      setQueryData<User>(queryUser.key(vars.id), (prev) => {
        if (!prev) return { id: vars.id, name: vars.name }
        return { ...prev, name: vars.name }
      })
    },
    invalidate: (_data, vars) => [queryUser.key(vars.id)],
  },
)

const addUser = createMutation((payload: User) => api.createUser(payload.name), {
  onMutate: (vars) => {
    setQueryData<User>(queryUser.key(vars.id), (prev) => {
      if (!prev) return { id: vars.id, name: vars.name }
      return { ...prev, name: vars.name }
    })
  },
  invalidate: (_data, vars) => [queryUser.key(vars.id)],
})

// UI

function UserInfo(props: { id: User['id'] }) {
  const data = useQuery(queryUser(props.id), {
    initialData: { id: props.id, name: 'Cerby the Dawg' },
  })
  return <pre>{JSON.stringify(data, null, 2)}</pre>
}

export function DataDemo() {
  // pretend this is from props.params or something
  const userId = crypto.randomUUID()

  const { mutate, data } = useMutation(updateUser)
  const { mutate: addUserMutate, data: addUserData } = useMutation(addUser)

  return (
    <Stack direction="column" justify="space-between" w="3/4">
      <Suspense fallback={<Box aria-busy h="200px" w="full" />}>
        <UserInfo id={userId} />
      </Suspense>

      <Stack direction="column">
        <Stack>
          <Text as="small">Cerby Mutation Data:</Text>
          <pre>{JSON.stringify(data, null, 2)}</pre>
        </Stack>
        <Stack>
          <Text as="small">Add Mutation Data:</Text>
          <pre>{JSON.stringify(addUserData, null, 2)}</pre>
        </Stack>
      </Stack>

      <ButtonGroup>
        <Button
          onClick={() =>
            mutate({ id: userId, name: `User ${crypto.randomUUID().slice(0, 8)}` })
          }
          size="sm"
        >
          Update Cerby
        </Button>
        <Button
          size="sm"
          usage="outlined-subtle"
          onClick={() =>
            addUserMutate({
              id: crypto.randomUUID(),
              name: `User ${crypto.randomUUID()}`,
            })
          }
        >
          Add User
        </Button>
      </ButtonGroup>
    </Stack>
  )
}

On this page

  • Usage
  • Using Mutations
  • Data
  • Status
  • API
  • Return
  • Edit this page on Github

User 498e9546-0775-4455-b882-b8f3425552cb

User a7491a3d-f461-49ae-af69-9ec3fd4774e1