[]previous and next page

Data with Convex

Data with Convex in the Next.js to ShadowJS guide.

For agentsUpdated

@shadow-js/convex mirrors convex/react. The function references still come from your generated api (npx convex codegen). What changes is that results are accessors and flags are properties or accessors. Convex runs in the browser against its own deployment, so it needs nothing from the server half of Dusk; use it instead of a loader and a server function when Convex already owns the data.

convex/react @shadow-js/convex
<ConvexProvider client={client}> Same, with a client from createConvexClient(url) or new ConvexClient(url)
useQuery(ref, args) returns data or undefined Returns a function: tasks() is the data or undefined. It also has status, isLoading and error properties
useQuery(ref, "skip") Same, and the args may be an accessor: () => (ready() ? args : "skip")
useMutation(ref) returns a function Returns a callable with pending(), error(), reset() and withOptimisticUpdate
useAction(ref) Same shape as useMutation, without optimistic updates
usePaginatedQuery(ref, args, { initialNumItems }) Same name; results(), status(), isLoading(), error() are accessors and loadMore(n) returns a boolean
useConvexAuth(), Authenticated, Unauthenticated, AuthLoading Same names; useConvexAuth() returns isLoading() and isAuthenticated() accessors
ConvexProviderWithClerk ConvexProviderWithAuth with useAuth={clerkConvexAuth({ template: "convex" })}
preloadQuery and fetchQuery from convex/nextjs preloadQuery(http, ref, args) returns { value, token }; use ConvexHttpClient for other one-shot calls

A Convex mutation replaces a server action, and a Convex query replaces a server component that reads the database.

jsx
"use client";
import { useMutation, useQuery } from "convex/react";
import { api } from "../convex/_generated/api";

export function Tasks() {
  const tasks = useQuery(api.tasks.list, {});
  const toggle = useMutation(api.tasks.toggle);
  if (tasks === undefined) return <p>Loading</p>;
  return (
    <ul>
      {tasks.map((task) => (
        <li key={task._id} onClick={() => toggle({ id: task._id })}>{task.title}</li>
      ))}
    </ul>
  );
}

Clerk and Convex together#

ConvexProviderWithAuth takes any hook that returns { isLoading, isAuthenticated, fetchAccessToken }, and clerkConvexAuth builds one from Clerk. Put ClerkProvider above ConvexProviderWithAuth:

src/root.tsxtsx
import { clerkConvexAuth } from "@shadow-js/clerk";
import { ConvexProviderWithAuth, type ConvexClientLike } from "@shadow-js/convex";
import { AuthRoot } from "./auth";

const useConvexAuth = clerkConvexAuth({ template: "convex" });

export function Root(props: { convex: ConvexClientLike; clerk?: import("@shadow-js/clerk").ClerkLike; children?: unknown }) {
  return (
    <AuthRoot {...(props.clerk === undefined ? {} : { clerk: props.clerk })}>
      <ConvexProviderWithAuth client={props.convex} useAuth={useConvexAuth}>
        {props.children}
      </ConvexProviderWithAuth>
    </AuthRoot>
  );
}