Docs

Optimistic UI & fetchers

Beyond the per-route action, Nifra gives you optimistic updates and independent, concurrent fetchers for snappy, granular mutations — agnostic across all five frameworks, each in its idiom.

Optimistic updates

While a submit is in flight, read the value back from the submission's formData (on a fetcher, fetcher.submission; on a route, the submission prop) and the UI reflects it immediately. When the action resolves the real data takes over, and a failed submit reverts automatically — no manual rollback bookkeeping.

TS
import { useFetcher } from "@nifrajs/web-react/fetcher"

// Optimistic UI: while a submit is in flight, read the expected value from the in-flight
// submission's FormData; the real data takes over when the action resolves, and a failed
// submit reverts automatically — no manual rollback.
const fetcher = useFetcher("todo:" + id)
const pending = fetcher.submission?.formData.get("done")        // the value you just submitted
const done = pending != null ? pending === "true" : todo.done   // optimistic value wins while pending

function toggle() {
  const fd = new FormData()
  fd.set("id", String(id))
  fd.set("done", String(!done))
  fetcher.submit("/todos", fd)   // submit(actionPath, body) — runs the route's action
}

Concurrent fetchers

useFetcher(key) (React) / createFetcher(key) (Solid) is an independent submission state machine — perfect for row-level actions or side-channel loads that shouldn't block navigation. useFetchers() exposes the live collection for global pending indicators. After a mutation, targeted revalidation refreshes just the affected data (via the X-Nifra-Revalidate header).

TS
import { useFetcher, useFetchers } from "@nifrajs/web-react/fetcher"

// Concurrent fetchers: each keyed fetcher is its own state machine, so many rows
// mutate in parallel without clobbering each other. `pending` is the in-flight flag.
function Row({ id }: { id: string }) {
  const f = useFetcher("row:" + id)
  const save = () => { const fd = new FormData(); fd.set("id", id); f.submit("/rows", fd) }
  return <button disabled={f.pending} onClick={save}>Save</button>
}

// useFetchers() exposes the live collection — e.g. a global "saving…" indicator.
const anyPending = useFetchers().some((f) => f.snapshot().pending)
Proudly built with Nifra — server-rendered on Cloudflare Pages.MIT