Resource, LazyCache, AutoMap
Resource
A Value that is loaded from somewhere else can in fact have 3 different states:
- Still loading
- Load failed and gave an error
- Load succeeded and gave proper result
This situation is represented by the Resource enum. Note, that you still need to wrap Resource with Value to keep it reactive!
use vertigo::{bind, dom, Resource, Value}; let my_value = Value::new(Resource::Loading); let set_ready = bind!(my_value, |_| my_value .set(Resource::Ready("OK".to_string()))); let set_error = bind!(my_value, |_| my_value .set(Resource::Error("Error!".to_string()))); let value_rendered = my_value.render_value(|value| { dom! { <p>{ match value { Resource::Loading => "Still loading".to_string(), Resource::Ready(val) => format!("Ready: {val}"), Resource::Error(err) => format!("Error: {err}"), } }</p> } }); dom! { <div> <p>"Current value: " {value_rendered}</p> <button on_click={set_ready} value="Set ready" /> <button on_click={set_error} value="Set error" /> </div> }
LazyCache
Even more handy way when loading a value from the internet is to use LazyCache. It caches the result for defined amount of time and refreshes upon expiry.
It can be created out of a RequestBuilder and inherits the ttl setting from it.
use vertigo::{AutoJsJson, RequestBuilder}; #[derive(AutoJsJson, PartialEq, Clone)] pub struct Model { id: i32, name: String, } let posts = RequestBuilder::get("https://some.api/posts") .ttl_seconds(300) .lazy_cache(|status, body| { if status == 200 { Some(body.into::<Vec<Model>>()) } else { None } });
Internally it holds an ApiResponse enum, which is a Resource<Rc<T>> + expiry time. You just need to provide a handler which gets HTTP status + body, which you need to convert to T or error.
When the page is rendered on the server, the responses fetched there are sent to the browser along with the HTML, so LazyCache doesn't fetch the same data again during hydration.
Refreshing
Besides refreshing upon expiry, the value can be refreshed on demand:
refresh().await- fetches the value now and waits until the new one is in the cache. The current value stays until then, without switching to the loading state,force_update(with_loading)- fetches the value now, without waiting for it,forget()- drops the value, so it will be fetched on next access.
use vertigo::{bind_spawn, dom, AutoJsJson, RequestBuilder, Resource}; #[derive(AutoJsJson, PartialEq, Clone)] pub struct Model { id: i32, name: String, } let posts = RequestBuilder::get("https://some.api/posts") .ttl_seconds(300) .lazy_cache(|status, body| { if status == 200 { Some(body.into::<Vec<Model>>()) } else { None } }); let list = posts.to_computed().render_value(|posts| match posts { Resource::Loading => dom! { <p>"Loading..."</p> }, Resource::Ready(posts) => dom! { <ul> {..posts.iter().map(|post| dom! { <li>{&post.name}</li> })} </ul> }, Resource::Error(err) => dom! { <p>"Error: " {err}</p> }, }); // The current list stays on the page until the new one arrives let refresh = bind_spawn!(posts, |_| async move { posts.refresh().await; }); dom! { <div> {list} <button on_click={refresh}>"Refresh"</button> </div> }
For lists edited by the user (CRUD), take a look at LazyListCache which keeps every item separately reactive and supports optimistic updates.
AutoMap
If your request depends on some parameter, for example a slug, you can use AutoMap to keep LazyCache value for every requested parameter set.
use vertigo::{AutoJsJson, AutoMap, RequestBuilder}; #[derive(AutoJsJson, PartialEq, Clone)] pub struct Model { id: i32, name: String, } let posts = AutoMap::new(|_myself, slug: &String| { let url = format!("https://some.api/posts/{slug}"); RequestBuilder::get(url) .ttl_seconds(300) .lazy_cache(|status, body| { if status == 200 { Some(body.into::<Model>()) } else { None } }) });
Constructor of AutoMap takes a generator which converts parameter to value. It also takes the AutoMap itself just in case, but can be safely ignored.