> ## Documentation Index
> Fetch the complete documentation index at: https://docs.bijection.com/llms.txt
> Use this file to discover all available pages before exploring further.

# React computation results

> Follow a maintained result, or start and follow one run

A computation's results reach React in one of two ways:

* **A dashboard follows the current result.** A maintained result refreshes in
  the background and the UI reads whichever accepted result is selected.
* **A user starts a run and follows it.** A report or simulation runs when the
  user asks, and the UI follows that specific run.

Both read through ordinary reactive queries. Rendering, remounting and
reconnecting never start work.

## Follow a maintained result

Declare the maintained result next to the computation. `latestResultPolicy`
is the common dashboard policy: refresh when the result is stale or its inputs
were invalidated, coalesce overlapping demand into one run in flight, and serve
each result as soon as it is accepted. Freshness, stale grace and retention
stay your choices.

```ts bijection/maintained.ts theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
import {
  latestResultPolicy,
  maintain,
} from "@bijection/pipelines/maintained";
import { componentsGeneric, makeFunctionReference } from "bijection/server";
import { internal } from "./_generated/api";
import { requireAnalyst } from "./policies";
import { aggregate } from "./revenue";

export const currentRevenue = maintain(
  componentsGeneric().pipelines as any,
  aggregate,
  {
    access: {
      policy: makeFunctionReference("application:pipelineAccess"),
      read: requireAnalyst,
      refresh: requireAnalyst,
    },
    args: async (ctx) => ctx.runQuery(internal.maintained.latestInput, {}),
    ...latestResultPolicy({
      minInterval: "1m",
      maxResultAge: "15m",
      staleGrace: "1h",
      retention: "1d",
    }),
  },
);

```

Override a single choice by spreading the policy first, for example
`select: { by: "manual" }` for a result a reviewer approves.

```tsx theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
import { useOutputs } from "bijection/react";
import { api } from "../bijection/_generated/api";

export function Revenue() {
  const view = useOutputs(api.maintained.currentRevenue, {
    revenue: { initialNumItems: 100 },
  });
  if (view.kind !== "available") return <p>{view.kind}</p>;
  const { results, status, loadMore } = view.outputs.revenue;
  return (
    <>
      <p>
        {view.selection?.serving}
        {view.selection?.isRefreshing ? " · refreshing" : ""}
      </p>
      <ul>
        {results.map((row) => (
          <li key={row.customer_id}>{row.revenue_minor.toString()}</li>
        ))}
      </ul>
      <button disabled={status !== "CanLoadMore"} onClick={() => loadMore(100)}>
        More
      </button>
    </>
  );
}
```

The UI reacts as soon as the selection or its serving state changes. The
calculation itself refreshes on its own schedule: a reactive read of an external
Spark result is current with its selection, not with your source tables. Use
`ctx.computations.read` in an ordinary query when a calculation must be
transactionally current with native tables.

## Run a calculation on demand

Declare a public computation with one access policy per control. Starting,
reading, cancelling and retiring are different permissions; reuse your own
policy helpers instead of assuming one implies another. An omitted `cancel` or
`retire` policy denies it. Holding a generated computation reference grants
nothing.

```ts bijection/reports.ts theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
import { computation } from "bijection/computations";
import { requireAnalyst, requireOwner } from "./policies";

export const revenue = computation({
  // args, returns and handler as for any computation
  access: {
    start: requireAnalyst,
    read: requireAnalyst,
    cancel: requireOwner,
    retire: requireOwner,
  },
});
```

The generated reference is `api.reports.revenue`. `useRun` starts and recovers
runs; `useOutputs(run, outputs)` follows one run's outputs. `useComputation`
composes both for a screen that starts and shows its own latest run.

```tsx theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
import { useComputation } from "bijection/react";
import { api } from "../bijection/_generated/api";

export function RevenueReport({ orders, day }: ReportInput) {
  const report = useComputation(api.reports.revenue, {
    interactionKey: `revenue:${day}`,
    outputs: { revenue: { initialNumItems: 100 } },
  });
  const { view } = report;
  return (
    <>
      <button
        disabled={!report.canStart}
        onClick={() => void report.start({ orders, day })}
      >
        Calculate
      </button>
      <button
        disabled={!report.canRetry}
        onClick={() => void report.retry()}
      >
        Recover
      </button>
      <p role="status">{report.feedback.message}</p>
      {view.kind === "pending" && <p>Run {view.run.phase}</p>}
      {view.kind === "available" &&
        view.outputs.revenue.results.map((row) => (
          <p key={row.customer_id}>{row.revenue_minor.toString()}</p>
        ))}
    </>
  );
}
```

`report.start(args)` resolves with the run once admission is established; rows
arrive later in `report.view`. Each `start` after a decision admits new work. To
keep showing another run, compose `useRun` and `useOutputs` directly.
`report.status` is one of:

| Status | Meaning |
| - | - |
| `restoring` | The interaction is reading its retained request after a mount |
| `ready` | Nothing is retained, or the last request ended without a run |
| `starting` | A submission is in flight |
| `unconfirmed` | A submission has no known decision, for example after a lost reply |
| `started` | `report.run` holds the admitted run |
| `incompatible` | The retained intent belongs to a different computation contract |

An `unconfirmed` submission keeps its identity. `retry()` resubmits the
original request with its original arguments and never creates a second run;
`start` refuses until the submission is resolved. The request journal survives
reloads, so remounting a component restores the same run.

## Read run state

`useOutputs`, like `useComputation`'s `view`, returns `idle`, `loading`,
`pending`, `unavailable` or `available`. A pending run reports a summary: its `phase`, whether cancellation
was requested, and the first error diagnostic. A run that ended without output
reports the same summary on its `absent` reason, so the UI can tell a failed run
from a cancelled one:

```tsx theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
if (view.kind === "unavailable" && view.reason.kind === "absent") {
  const { phase, diagnostic } = view.reason.run;
  return <p>Run {phase}{diagnostic ? `: ${diagnostic.message}` : ""}</p>;
}
```

The `inspect` endpoint keeps the complete inspection protocol for debugging:
attempts, evidence, custody, measurements and every diagnostic.

## Pages and errors

Row outputs use the same shape as `usePaginatedQuery`: `results`, `status`
(`CanLoadMore`, `LoadingMore` or `Exhausted`), `isLoading` and
`loadMore(numItems)`. The first page arrives with the observation; select its
size with `initialNumItems`.

* Expected lifecycle conditions are typed state: `pending`, or `unavailable`
  with an `absent`, `unknown`, `withheld` or `unavailable` reason.
* A failed first read throws from the hook, like `useQuery`, and reaches your
  error boundary.
* A failed continuation keeps every row already loaded and sets that output's
  `error` to the query's own error, including `ConvexError` data. The page stays
  subscribed.
* A changed maintained selection, revoked access or withheld result discards
  loaded rows; pages from different selections are never mixed.

## Select exact inputs

Dataset inputs are exact versions chosen on the server. The dataset owner
returns the typed input directly:

```ts theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
const datasets = new Datasets(components.datasets);
const latest = await datasets.latestSnapshot(ctx, orders); // null before the first version
const pinned = await datasets.snapshot(ctx, orders, versionId);
```

Admission acquires its own pin on the selected version and checks access again.
A run keeps that version even after newer ones are published.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.