DocsBlog
  • 1.1.2

  • light

    dark

    system

    Switch mode
  • Cerberus

    Acheron

    Elysium

Get Started
Components
Data Grid
Signals
Styling
Theming

Get Started

OverviewReactivityData FetchingStores

Primitives

createSignalcreateQuerycreateMutationcreateComputedcreateEffectcreateStoreContextbatch

Hooks

useQueryuseMutationuseReaduseSignal

Components

ReactiveText

On this page

  • Edit this page on Github

Signals Data Fetching

Learn the fundamentals of fetching data using Signals.

  • source

Introduction

Fetching data from a remote API or database is a core task for most applications. Cerberus Signals provide foundational tools like the createQuery primitive and createMutation to manage asynchronous data.

Fetching data with signals will automatically cache the response until it is invalidated through a mutation.

Fetching Data

To fetch (and cache) data using signals, simply follow two steps:

  1. Define a query
  2. Trigger the query via the useQuery hook in your component

In this example there are a few things happening:

  1. The "backend API"
  2. The query
  3. The component using the query
  4. An action that updates the global currentUser state

When you pass an Accessor into the query definition, it will auto-fetch, invalidate, and cache the result when the signal Accessor updates. This means, with this design mutations are not neccessary.

Note

Refresh the page to see the cached response. This happens because we are passing a signal accessor as the first value.

Mutating Data

Sometimes, you may have a complex scenario where that prevents you from using a signal utilized as the query "data store". This is where mutations come in.

In order to mutate non-signal based data, follow these steps:

  1. Init the query with a non-accessor function
  2. Define the mutation. Optional: pass the query.key into invalidate to clear the cache for that query.
  3. Use the mution action

Here's what's happening in this demo:

  1. A non-signal based store (e.g. database)
  2. A query that does not watch an Accessor signal
  3. A mutation that updates the "db" and invalidates the user query cache
  4. A button calling the mutate action to trigger the mutation callback and invalidation

Note

Anytime a non-signal accessor is passed to a query, it bypasses the auto functionality and converts it to only be manually updated via mutations.

{
  "id": "411809ca-e360-4f6f-aa8f-2b0e574dac8b",
  "name": "User 411809ca-e360-4f6f-aa8f-2b0e574dac8b"
}
Copy
'use client'

import { Box, Stack } from '@/styled-system/jsx'
import { Button } from '@cerberus/react'
import { createQuery, createSignal, useQuery } from '@cerberus/signals'
import { Suspense } from 'react'

type User = {
  id: string
  name: string
}

function fetchUser(id: User['id']): Promise<User> {
  return new Promise<User>((resolve) => {
    setTimeout(() => {
      resolve({ id, name: `User ${id}` })
    }, 1000)
  })
}

const [currentUser, setCurrentUser] = createSignal<User['id']>(crypto.randomUUID())

// 1. Define the query
const query = createQuery(currentUser, fetchUser)

function UserInfo() {
  // 2. Trigger the query
  const data = useQuery(query)
  return <pre>{JSON.stringify(data, null, 2)}</pre>
}

export function BasicDemo() {
  return (
    <Stack direction="column" justify="space-between" w="3/4">
      <Suspense fallback={<Box aria-busy h="96px" rounded="sm" w="full" />}>
        <UserInfo />
      </Suspense>

      <Button onClick={() => setCurrentUser(crypto.randomUUID())}>Change User</Button>
    </Stack>
  )
}
{
  "id": "",
  "name": ""
}
Copy
'use client'

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

// Fake DB

type User = {
  id: string
  name: string
}

let dbUser: User = { id: '', name: '' }

// Data fetching

const query = createQuery(
  () => 'init',
  async () => dbUser,
)

const mutation = createMutation(
  async (newId: User['id']) => {
    dbUser = { id: newId, name: `User ${newId}` }
    return dbUser
  },
  {
    invalidate: () => [query.key],
  },
)

// UI

function UserInfo() {
  const data = useQuery(query)
  return <pre>{JSON.stringify(data, null, 2)}</pre>
}

export function MutationDemo() {
  const { mutate } = useMutation(mutation)

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

      <Button onClick={() => mutate(crypto.randomUUID())}>Change User</Button>
    </Stack>
  )
}