Skip to content

03 — Data fetching

use(async fn) returns a resource. Wrap it in <Suspense> for loading UI and <ErrorBoundary> for failures.

tsx
import { use } from 'flexium/core'
import { Suspense, ErrorBoundary } from 'flexium/dom'

type User = { id: number; name: string; email: string }

function UserProfile({ id }: { id: number }) {
  const [user] = use(async () => {
    const res = await fetch(`https://jsonplaceholder.typicode.com/users/${id}`)
    if (!res.ok) throw new Error(`HTTP ${res.status}`)
    return res.json() as Promise<User>
  })

  // `user` is the resolved value at this point — Suspense pauses render until it's ready
  return (
    <article>
      <h2>{user.name}</h2>
      <a href={`mailto:${user.email}`}>{user.email}</a>
    </article>
  )
}

export function App() {
  return (
    <ErrorBoundary fallback={(err) => <p style="color:red">Failed: {err.message}</p>}>
      <Suspense fallback={<p>Loading user…</p>}>
        <UserProfile id={1} />
      </Suspense>
    </ErrorBoundary>
  )
}

How it works

  • use(async fn) runs fn, captures the promise, and registers the component for suspension.
  • While the promise is pending, <Suspense fallback={...}> shows the fallback.
  • When the promise resolves, the component re-renders with the value.
  • If the promise rejects, <ErrorBoundary fallback={(err) => ...}> catches it.

The async function only re-runs when its captured signals change — referential equality is enough.

Reactive fetch on a signal

To re-fetch when an input changes, derive the resource from a signal:

tsx
function UserSearch() {
  const [id, setId] = use(1)
  const [user] = use(async () => {
    // `id` is captured — fetch re-runs when `id` changes
    const res = await fetch(`/api/users/${id}`)
    return res.json()
  })

  return (
    <div>
      <input type="number" value={id} onchange={(e: any) => setId(+e.target.value)} />
      <Suspense fallback={<p>Loading…</p>}>
        <p>{user.name}</p>
      </Suspense>
    </div>
  )
}

Error recovery

<ErrorBoundary> accepts a reset prop or a render-prop fallback that receives the error:

tsx
<ErrorBoundary fallback={(err, reset) => (
  <div>
    <p>Error: {err.message}</p>
    <button onclick={reset}>Try again</button>
  </div>
)}>
  <UserProfile id={1} />
</ErrorBoundary>

API surface used

  • use(async fn) — resource (Promise-aware signal)
  • <Suspense fallback={...}> — declarative loading state
  • <ErrorBoundary fallback={(err) => ...}> — declarative error state

Next

Form validation — controlled inputs, derived state.

Released under the MIT License.