Resources
Turn asynchronous fetches into signals, and show a fallback while they load.
A resource turns an asynchronous fetch into a signal, and View.Suspense shows a fallback while the resources below it load for the first time.
Info: A resource is a
Signal.tof its loading state. Read it withSignal.getlike any other signal.
Resources
Creating a Resource
Resource.make takes a function that returns a promise and starts fetching right away:
1let userId = Signal.make("1")23let user = Resource.make(async () => {4 let id = Signal.get(userId) // a dependency: read before the first await5 let response = await fetch(`/api/users/${id}`)6 await response->json7})
The fetch runs in an effect. The signals it reads before its first await are its dependencies: change userId and the resource fetches again. Reads after an await are not tracked.
Reading the State
A resource holds one of four states:
Pending: fetching with no value yet, on the first fetch or after a failure.Ready(value): the latest fetch settled withvalue.Refreshing(value): fetching again, with the previousvaluekept.Failed(exn): the latest fetch threw or rejected.
1module UserName = {2 @xote.component3 let make = () =>4 <p>5 {switch Signal.get(user) {6 | Pending => "Loading..."7 | Ready(user) | Refreshing(user) => user.name8 | Failed(_) => "Could not load the user"9 }}10 </p>11}
Resource.value returns the value a state carries, if any, and Resource.isLoading tells whether a fetch is in flight.
Refetching and Mutating
Resource.refetch fetches again with the same dependencies. Resource.mutate sets the value directly, which is useful for optimistic updates, and drops any fetch in flight.
1<button onClick={_ => Resource.refetch(user)}> {"Reload"} </button>
Only the latest fetch can settle the state. If the user switches from one id to another and the first response arrives last, it is ignored.
Errors
A failed fetch is a state like any other. To send it to a View.ErrorBoundary instead, throw it where the state is read:
1switch Signal.get(user) {2| Failed(exn) => throw(exn)3| state => ...4}
Suspense
Showing a Fallback
Wrap the components that create resources in View.Suspense:
1<View.Suspense fallback={<p> {"Loading..."} </p>}>2 <Profile id />3 <Posts id />4</View.Suspense>
The fallback shows until every resource created below the suspense has loaded once. The children render straight away, hidden, so their fetches start at once and their state survives the wait. Their refs and onMount callbacks are held back until they are shown.
Rules
- A resource suspends the nearest
Suspenseabove the component that created it, wherever it is read. - A resource counts once. A later fetch shows as
Refreshingand never brings the fallback back. - Suspenses nest. The nearest one waits, and an inner subtree that is ready stays hidden while an outer suspense still waits.
- Removing a component releases its resources: a fetch in flight is dropped, and a suspense waiting for it stops waiting.
Server Rendering
During a server render (SSR.renderToString) nothing is awaited: no fetch starts, and Suspense renders its fallback. A resource created outside a render, at module level in a server process, is not covered and does fetch; create resources in components. Hydration then renders the children on the client, where the resources fetch.
Next Steps
- Learn how effects track their dependencies in Effects.
- Handle failures with error boundaries.