Recipes
Search as you type
Use this for a search box that calls the API as the user types. Each keystroke starts a request, and the answers can come back out of order. A slow answer for r can arrive after a fast one for rea, and the list then shows results for a query that is no longer in the box.
Set dedupe: true on the endpoint. Each new call cancels the one still in flight, so only the latest search can reach the screen. Repo is your result type; render and showError stand for your UI code.
import { createApi, defineRequest } from 'liaise'
const search = defineRequest<Repo[], { q: string }>()({
method: 'GET',
path: '/search',
dedupe: true, // a new call cancels the one still in flight
})
const api = createApi({ baseUrl: '/api', requests: { search } })
async function onInput(q: string) {
const { data, error } = await api.search({ q })
if (error?.kind === 'abort') return // a newer search replaced this one
if (error) return showError(error)
render(data)
}
Why it works
Typing r, re and rea makes three calls. Each new call cancels the one before it, so the calls for r and re settle with kind: 'abort', and onInput returns before it renders anything. If the server answers r last, that answer has already been dropped. Only the results for rea reach render.
- A cancelled call isn’t an error to show.
'abort'also covers a cancel of your own, andonErrornever hears about it (The six kinds). dedupeworks per endpoint. A new search never cancels a call to another endpoint.dedupedoesn’t send fewer requests. Every keystroke still starts a call, which cancels the one before. To send fewer, callonInputonly once the user pauses typing (a debounce).sharedoes the opposite: it joins the call already running instead of cancelling it. Setting both on one endpoint throws when you create the client.
Drop stale calls with dedupe has the rest of dedupe’s rules.
Next: Use with TanStack Query connects liaise to TanStack Query’s caching and refetching.