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

Data Fetching

Learn the fundamentals of fetching data using Signals.

  • 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

Introduction

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

Cerberus Signals data fetching APIs come with the following benefits:

  1. High performing: our benchmarks outperform Tanstack queries by over 60% in some areas
  2. Caching: all queries are cached until invalidated
  3. Optimistic updates: built in support for "real-time" optimistic UI updates
  4. Data Streaming: compatible with Async Generators for LLM responses
  5. SSR Sync: fetch data on the server and sync it to a client-side query
  6. Suspense: native support for React Suspense
  7. Error Boundaries: native support for Error Boundaries

Fetching Data

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

  1. Define a query factory via createQuery
  2. Use the factory via useQuery in your component

In this example we use a Signal to trigger a new query request. When using this design you, opt-out of optimistic UI updates and fallback to legacy loading-based UI changes.

Loading example...

In this example there are a few things happening:

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

When you pass an Signal 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 - but still strongly recommended.

Optimistically Updating Query Data

When you want to perform an action related to a query, you utilize a mutation factory via createMutation.

When combined with query.key, this factory will automagically sync and update the query if it is listed in the invalidate options.

Even more, when combined with onMutate/onSetData, the UI will optimistically update while the query runs in the background creating a "real-time" like experience in the UI.

Loading example...

Here's what's happening in this demo:

  1. A query factory is created
  2. A mutation factory is created a. onMutate/setQueryData provides optimistic updates to the query b. invalidate breaks the query cache to ensure the latest data is fetched in the background
  3. The UI is automagically synced with the query data
  4. Actions call the mutate helper to trigger mutations.

Server Component Pattern

In an SSR environment, you execute the factory's raw fetcher directly, bypassing the reactive cache. Then, you pass that data down to your Client Components to "hydrate" or seed the Cerberus cache, ensuring the client doesn't double-fetch on mount.

This is the standard SSR pattern for React.

1. Server Component (Fetching)

Expose the raw, stateless fetcher function from your factory. You simply await it like a standard asynchronous function.

Loading example...

2. Client Component

On the client side, use the initialData property. If the Cerberus cache is empty, it will instantly seed the cache with the server's data, skipping the <Suspense> boundary entirely.

Loading example...

Streaming Reponses (Async Generators)

Cerberus queries also support streaming data via Async Generators. This is powerful if you are using an LLM API or your local API supports data streaming.

Loading example...

On this page

  • Introduction
  • Fetching Data
  • Optimistically Updating Query Data
  • Server Component Pattern
    • 1. Server Component (Fetching)
    • 2. Client Component
  • Streaming Reponses (Async Generators)
Edit this page on Github
{
  "id": "16aad9b1-67e2-443b-bb09-df6cf0ee4f49",
  "name": "User 16aad9b1-67e2-443b-bb09-df6cf0ee4f49"
}

User ac732fed-e3b9-4638-9ff9-9e771964008a

idle
Copy
// app/users/[id]/page.tsx (Server Component)

import { UserProfile } from './client.demo'
import { queryUser } from './queries'

interface Props {
  params: Promise<{ id: string }>
}

export default async function UserPage({ params }: Props) {
  const { id } = await params
  // Bypass the reactive cache and execute the raw fetcher directly.
  // This is completely memory-safe for Node.js environments.
  const initialUserData = await queryUser.fetcher(id)

  if (!initialUserData) return null

  return (
    <main>
      {/* 2. Pass the fetched data to the Client Component */}
      <UserProfile id={id} initialData={initialUserData} />
    </main>
  )
}
Copy
'use client'

import { useQuery } from '@cerberus/react/signals'
import { queryUser } from './queries'
import { type User } from './db'

interface Props {
  id: string
  initialData: User
}

export function UserProfile(props: Props) {
  // 1. Cerberus sees `initialData`, instantly seeds the $O(1) Map,
  // and mounts the component with zero loading spinners or network waterfalls.
  const user = useQuery(queryUser(props.id), { initialData: props.initialData })

  if (!user) return null

  return <h1>{user.name}</h1>
}