Core
Async
Load data with resource, and say what to show while it loads with Boundary. On the server, weft can wait for that data so the first page arrives filled in.
A save button that waits 800 ms, standing in for a real request, and disables itself meanwhile.
ProfileSaves: 0
import { state } from '@weft-js/core'import { Badge, Button, Card, Spinner } from '@weft-js/warp'const save = () => new Promise(done => { setTimeout(done, 800) })export default function Profile() { const saving = state(false) const saves = state(0) const go = async () => { saving.value = true await save() saves.value++ saving.value = false } return ( <Card title="Profile"> <Badge tone="positive">Saves: {saves.value}</Badge> <Button disabled={saving.value} onClick={go}> {saving.value ? <Spinner /> : 'Save changes'} </Button> </Card> )}- Try this
- Press Save changes, and try pressing again while it spins.
- Why it matters
- Async code is plain async and await. State you change after an await updates the page like any other.
tsx
import { resource, Boundary } from '@weft-js/core'type User = { name: string }function Name({ id }: { id: string }) { // `id` is read before the first `await`, so a new id fetches again and aborts the old request. const user = resource(signal => fetch(`/api/users/${id}`, { signal }).then(r => r.json() as Promise<User>)) // `pending` is also true on a refetch, while the old value is still here: dim it rather than replace it (X033). return <h1 aria-busy={user.pending ? 'true' : 'false'}>{user.value?.name}</h1>}// The resource is made inside the Boundary (in Name), so the Boundary waits for it.export function Profile({ id }: { id: string }) { return ( <Boundary pending={<p>Loading…</p>} error={(e, retry) => <button onClick={retry}>Retry</button>}> <Name id={id} /> </Boundary> )}resource(fetcher)is asyncderived:.value,.pending,.error,.refresh(). It re-fetches when what it read before the firstawaitchanges, and aborts the superseded fetch throughsignal.derived(async …)is W012.<Boundary pending error>covers every resource created while its children are built, and every error they throw. A resource made above the<Boundary>in the same component isn't below it, and the fallback never shows (X037). The fallback shows after 150 ms and stays at least 400 ms; refetches keep the current content (no flash).error(e, retry)gets failed resources and children that throw during render.@weft-js/core/ssr:renderToString(() => <App />)renders what it can now;await renderToStringAsync(() => <App />)waits for resources first and puts their values in the page. Pass{ hydratable: true }when the client hydrates.renderToStream(() => <App />, { hydratable: true })sendsshellat once, then one chunk per<Boundary>as its resources resolve (chunks, an async iterable), anddata()forhydratewhen they're done.@weft-js/startstreams this way for you.hydrate(() => <App />, el)from@weft-js/core/domtakes over server HTML without rewriting it. Use it instead ofrenderwhen the HTML came from@weft-js/core/ssr.- Import only
renderToString,renderToStringAsyncandrenderToStreamfrom@weft-js/core/ssr; its other exports are what compiled server code calls.