Working with HTTP

All API calls go through the HyperFetch SDK. The SDK is typed end-to-end from Hono backend routes, so you get autocomplete and compile-time checks for every request.

Setup

The SDK is defined in src/api/sdk.ts:

import { createSdk } from "@hyper-fetch/core";
import type { ApiRoutesSdk } from "@hype-stack/backend";

import { client } from "./client";

export const sdk = createSdk<typeof client, ApiRoutesSdk>(client);

The ApiRoutesSdk type is generated from the backend's Hono routes. New endpoints are available immediately on the SDK with no manual wiring.

Reading data

Use useFetch for GET requests. It fetches on mount and caches the response:

import { useFetch } from "@hyper-fetch/react";
import { sdk } from "@/api/sdk";

function ProjectList() {
  const { data, loading, error, refetch } = useFetch(sdk.projects.$get);

  if (loading) return <Spinner />;
  if (error) return <ErrorMessage error={error} />;

  return (
    <ul>
      {data?.items.map((project) => (
        <li key={project.id}>{project.name}</li>
      ))}
    </ul>
  );
}

Mutations

Use useSubmit for POST, PUT, PATCH, DELETE. Pass the request body under payload, and read the result from the destructured tuple submit returns:

import { useSubmit } from "@hyper-fetch/react";
import { sdk } from "@/api/sdk";

function CreateProject() {
  const { submit, submitting } = useSubmit(sdk.projects.$post);

  const handleCreate = async () => {
    const { error } = await submit({ payload: { name: "New project" } });
    if (error) {
      toast.error("Failed to create project");
      return;
    }
    toast.success("Project created");
  };

  return (
    <button onClick={handleCreate}>
      {submitting ? "Creating..." : "Create"}
    </button>
  );
}

Side-effect callbacks

useSubmit returns onSubmitSuccess and onSubmitError functions. Register them at the top level of your component, they aren't options on the hook. The success callback receives { response }, where response.data holds the typed payload:

function CreateProject() {
  const { submit, submitting, onSubmitSuccess, onSubmitError } = useSubmit(sdk.projects.$post);

  onSubmitSuccess(({ response }) => {
    toast.success(`Created ${response.data.name}`);
  });

  onSubmitError(() => {
    toast.error("Failed to create project");
  });

  return (
    <button onClick={() => submit({ payload: { name: "New project" } })}>
      {submitting ? "Creating..." : "Create"}
    </button>
  );
}

Cache invalidation

When a mutation affects data shown elsewhere, invalidate the cache so all useFetch hooks refetch:

import { client } from "@/api/client";

client.cache.invalidate(/\/projects/);

Always import client from @/api/client directly. Don't reach through an SDK request to access the cache.

Query params and path params

Set query params or path params on the request before passing it to a hook:

const request = sdk.projects.$projectId.$get.setParams({ projectId: "abc" });
const { data } = useFetch(request);

For query params:

const request = sdk.notifications.$get.setQueryParams({ limit: "5", status: "unread" });
const { data } = useFetch(request);