Async Resources and Suspense
In any real non-trivial app, you probably need to fetch data from somewhere. This is where resources and suspense come in.
The resources API provides a simple interface for fetching and refreshing async data and suspense makes it easy to render fallbacks while the data is loading.
The Resources API
To create a new resource, call the create_isomorphic_resource
function.
use sycamore::prelude::*;
use sycamore::web::create_isomorphic_resource;
struct Data {
// Define the data format here.
}
async fn fetch_data() -> Data {
// Perform, for instance, an HTTP request to an API endpoint.
}
let resource = create_isomorphic_resource(fetch_data);
A Resource<T>
is a wrapper around a Signal<Option<T>>
. The value is
initially set to None
while the data is loading. It is then set to Some(...)
containing the value of the loaded data. This makes it convenient to display the
data in your view.
view! {
(if let Some(data) = resource.get_clone() {
view! {
...
}
} else {
view! {}
})
}
Note that create_isomorphic_resource
, as the name suggests, runs both on the
client and on the server. If you only want data-fetching to happen on the
client, you can use create_client_resource
which will never load data on the
server.
Right now, we do not yet have a create_server_resource
function which only
runs on the server because this requires some form of data-serializaation and
server-integration which we have not fully worked out yet.
Refreshing Resources
Resources can also have dependencies, just like memos. However, since resources
are async, we cannot track reactive dependencies like we would in a synchronous
context. Instead, we have to explicitly specify which dependencies the resource
depends on. This can be accomplished with the on(...)
utility function.
let id = create_signal(12345);
let resource = create_resource(on(id, move || async move {
fetch_user(id.get()).await
}));
Under the hood, on(...)
simply creates a closure that first accesses id
and
then constructs the future. This makes it so that we access the signal
synchronously first before performing any asynchronous tasks.
Suspense
With async data, we do not want to show the UI until it is ready. This problem
is solved by the Suspense
component and related APIs. When a Suspense
component is created, it automatically creates a new suspense boundary. Any
asynchronous data accessed underneath this boundary will automatically be
tracked. This includes accessing resources using the resources API.
Using Suspense
lets us set a fallback view to display while we are loading the
asynchronous data. For example:
view! {
Suspense(fallback=move || view! { LoadingSpinner {} }) {
(if let Some(data) = resource.get_clone() {
view! {
...
}
} else {
view! {}
})
}
}
Since we are accessing resource
under the suspense boundary, our Suspense
component will display the fallback until the resource is loaded.
Transition
Resources can also be refreshed when one of its dependencies changes. This will cause the surrounding suspense boundary to be triggered again.
This is sometimes undesired. To prevent this, just replace Suspense
with
Transition
. This component will continue to show the old view until the new
data has been loaded in, providing a smoother experience.