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

Creating Queries

Learn how to create cached queries in React.

  • source
import { createQuery, useQuery } from '@cerberus/signals'

Usage

createQuery allows you define a fetcher that can be auto-fetched and cached when partnered with a signal Accessor. This creates a streamlined and high-performant way to fetch data within your application with less effort.

The return value of a query is inteded to be used with useQuery.

A query is just a fancy wrapper that subscribes to the signals Observer. This means that it can be used for any scenario that is Promise-based - not just data-fetching.

See the features table to understand how to best utilize queries for each scenario you may have.

Creating Queries

Creating a query requires two things:

  1. A signal or signal-like accessor to subscribe to
  2. A Promise to call when activated

With Signals

If you pass a signal Accessor as the first argument, the query will subscribe to that signal and auto-call the Promise provided when the signal value changes. Likewise, the result of the Promise will then be cached until invalidated via a mutation.

When using a query bound to a signal there is no need for creating mutations. You simply update the signal it is subscribed to.

Caching lifecycle:

  1. First call → fetch data and cache
  2. Any call with same signal value → return cached value
  3. Signal updates → fetch data and cache

Without Signals

When you pass in an signal-like function that is not a true Accessor, you forfeit any auto-fetching and caching features. This means you must utilize mutations in order to update the state via the createMutation APIs.

Using Queries

createQuery only defines a query and does not actually call it. To use the query you must pass the returned value into the useQuery hook which reads the signal-based result.

const data = useQuery(myQuery)

The value returned is the result from calling the signal-tracked Accessor. This is equivalent to the return value from the useRead hook.

Features Table

FeatureWith SignalWithout Signal
auto-intial fetch✅✅
auto-refresh fetch✅❌
caching✅❌
requires mutation❌✅

API

createQuery accepts the following options:

ParamsRequiredDescription
Accessor<T> | FunctiontrueThe signal to subscribe to.
Promise<any>trueThe Promise to call when activated.

Return

createQuery returns the value of the Promise along with some additional properties:

PropertyTypeDescription
keyUUIDA uniqe ID to be referenced in the mutation invalidate Array.
currentArgsArgsThe args passed into createQuery stored in a reference.

Note

The query.key is SHA generated from the contents of the query parameters. This means, if you have multiple signal-like queries, they should each have unique return values or else they will be synced together.

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>
  )
}
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>
  )
}
{
  "id": "",
  "name": ""
}
{
  "id": "5d621392-a412-444d-b29b-4108ea9015cd",
  "name": "User 5d621392-a412-444d-b29b-4108ea9015cd"
}