mirror of
https://github.com/leptos-rs/leptos
synced 2024-11-10 06:44:17 +00:00
Async/Resource support and example
This commit is contained in:
parent
b21973783d
commit
46ea459988
14 changed files with 233 additions and 20 deletions
4
TODO.md
4
TODO.md
|
@ -1,5 +1,7 @@
|
|||
- [ ] Async
|
||||
- [ ] Resource
|
||||
- [x] Resource
|
||||
- [ ] Render bug: when doing e.g., a `match` and having multiple branches with separate `template`s, all exist as separate document fragment and replace the fragment, not one another -- find a way to make sure that `Child<'a>` returned from `insert` is _actually_ attached to the DOM
|
||||
- [ ] Render bug with list reconciliation
|
||||
- [ ] Suspense
|
||||
- [ ] Docs (and clippy warning to insist on docs)
|
||||
- [ ] Read through + understand...
|
||||
|
|
19
examples/fetch/Cargo.toml
Normal file
19
examples/fetch/Cargo.toml
Normal file
|
@ -0,0 +1,19 @@
|
|||
[package]
|
||||
name = "fetch"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.58"
|
||||
leptos = { path = "../../leptos" }
|
||||
reqwasm = "0.5.0"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
wee_alloc = "0.4"
|
||||
|
||||
[dev-dependencies]
|
||||
wasm-bindgen-test = "0.3.0"
|
||||
|
||||
[profile.release]
|
||||
codegen-units = 1
|
||||
lto = true
|
||||
opt-level = 'z'
|
7
examples/fetch/index.html
Normal file
7
examples/fetch/index.html
Normal file
|
@ -0,0 +1,7 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<link data-trunk rel="rust" data-wasm-opt="z"/>
|
||||
</head>
|
||||
<body></body>
|
||||
</html>
|
66
examples/fetch/src/lib.rs
Normal file
66
examples/fetch/src/lib.rs
Normal file
|
@ -0,0 +1,66 @@
|
|||
use anyhow::Result;
|
||||
use leptos::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct Cat {
|
||||
url: String,
|
||||
}
|
||||
|
||||
async fn fetch_cats(count: u32) -> Result<Vec<String>> {
|
||||
if count > 0 {
|
||||
log!("fetching cats");
|
||||
let res = reqwasm::http::Request::get(&format!(
|
||||
"https://api.thecatapi.com/v1/images/search?limit={}",
|
||||
count
|
||||
))
|
||||
.send()
|
||||
.await?
|
||||
.json::<Vec<Cat>>()
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(|cat| cat.url)
|
||||
.collect::<Vec<_>>();
|
||||
log!("got cats {res:?}");
|
||||
Ok(res)
|
||||
} else {
|
||||
Ok(vec![])
|
||||
}
|
||||
}
|
||||
|
||||
pub fn fetch_example(cx: Scope) -> web_sys::Element {
|
||||
let (cat_count, set_cat_count) = cx.create_signal::<u32>(0);
|
||||
let cats = cx.create_resource(cat_count.clone(), |count| fetch_cats(*count));
|
||||
|
||||
cx.create_effect(move || log!("cats data = {:?}", cats.data.get()));
|
||||
|
||||
view! {
|
||||
<div>
|
||||
<label>
|
||||
"How many cats would you like?"
|
||||
<input type="number"
|
||||
on:input=move |ev| {
|
||||
let val = event_target_value(&ev).parse::<u32>().unwrap_or(0);
|
||||
log!("set_cat_count {val}");
|
||||
set_cat_count(|n| *n = val);
|
||||
}
|
||||
/>
|
||||
</label>
|
||||
{match &**(cats.data.get().guard()) {
|
||||
None => view! { <p>"Loading..."</p> },
|
||||
Some(Err(e)) => view! { <pre>"Error: " {e.to_string()}</pre> },
|
||||
Some(Ok(cats)) => view! {
|
||||
<div>{
|
||||
cats.iter()
|
||||
.map(|src| {
|
||||
view! {
|
||||
<img src={src}/>
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
}</div>
|
||||
},
|
||||
}}
|
||||
</div>
|
||||
}
|
||||
}
|
9
examples/fetch/src/main.rs
Normal file
9
examples/fetch/src/main.rs
Normal file
|
@ -0,0 +1,9 @@
|
|||
use fetch::fetch_example;
|
||||
use leptos::*;
|
||||
|
||||
#[global_allocator]
|
||||
static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;
|
||||
|
||||
pub fn main() {
|
||||
mount_to_body(fetch_example)
|
||||
}
|
|
@ -294,23 +294,6 @@ pub fn pick_up_text_node(parent: &web_sys::HtmlElement, node_idx: usize) -> Opti
|
|||
None
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub fn spawn_local<F>(fut: F)
|
||||
where
|
||||
F: Future<Output = ()> + 'static,
|
||||
{
|
||||
wasm_bindgen_futures::spawn_local(fut)
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub fn spawn_local<F>(_fut: F)
|
||||
where
|
||||
F: Future<Output = ()> + 'static,
|
||||
{
|
||||
// noop for now; useful for ignoring any async tasks on the server side
|
||||
// could be replaced with a Tokio dependency
|
||||
}
|
||||
|
||||
pub fn remove_event_listeners(el: &web_sys::Element) {
|
||||
let clone = el.clone_node().unwrap_throw();
|
||||
replace_with(el, clone.unchecked_ref());
|
||||
|
|
|
@ -64,6 +64,7 @@ pub fn reconcile_arrays(parent: &web_sys::Element, a: &mut [web_sys::Node], b: &
|
|||
} else if b_end == b_start {
|
||||
// Remove.
|
||||
for node in &a[a_start..a_end] {
|
||||
crate::debug_warn!("unwrap at 68");
|
||||
if map.is_none() || !map.as_ref().unwrap().contains_key(&NodeWrapper(&node)) {
|
||||
parent.remove_child(node);
|
||||
}
|
||||
|
@ -97,6 +98,7 @@ pub fn reconcile_arrays(parent: &web_sys::Element, a: &mut [web_sys::Node], b: &
|
|||
.collect();
|
||||
map = Some(tmp);
|
||||
}
|
||||
crate::debug_warn!("unwrap at 102");
|
||||
let map = map.as_ref().unwrap();
|
||||
|
||||
if let Some(&index) = map.get(&NodeWrapper(&a[a_start])) {
|
||||
|
|
|
@ -126,6 +126,13 @@ pub fn insert_expression<'a>(
|
|||
mut current: Child<'a>,
|
||||
before: Option<&web_sys::Node>,
|
||||
) -> Child<'a> {
|
||||
crate::warn!(
|
||||
"insert {:?} on {} to replace {:?}",
|
||||
new_value,
|
||||
parent.node_name(),
|
||||
current
|
||||
);
|
||||
|
||||
if new_value == ¤t {
|
||||
current
|
||||
} else {
|
||||
|
@ -170,6 +177,11 @@ pub fn insert_expression<'a>(
|
|||
}
|
||||
}
|
||||
Child::Node(old_node) => {
|
||||
crate::warn!(
|
||||
"replacing old node with new node\n\nparents are {} and {}",
|
||||
old_node.parent_node().unwrap().node_name(),
|
||||
node.parent_node().unwrap().node_name()
|
||||
);
|
||||
replace_with(old_node.unchecked_ref(), node);
|
||||
Child::Node(node.clone())
|
||||
}
|
||||
|
|
|
@ -4,4 +4,7 @@ version = "0.1.0"
|
|||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
bumpalo = "3"
|
||||
bumpalo = "3"
|
||||
|
||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||
wasm-bindgen-futures = "0.4"
|
|
@ -5,15 +5,19 @@
|
|||
#![feature(unboxed_closures)]
|
||||
|
||||
mod effect;
|
||||
mod resource;
|
||||
mod root_context;
|
||||
mod scope;
|
||||
mod scope_arena;
|
||||
mod signal;
|
||||
mod spawn;
|
||||
|
||||
pub use effect::*;
|
||||
pub use resource::*;
|
||||
pub use root_context::*;
|
||||
pub use scope::*;
|
||||
pub use signal::*;
|
||||
pub use spawn::*;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
|
67
leptos_reactive/src/resource.rs
Normal file
67
leptos_reactive/src/resource.rs
Normal file
|
@ -0,0 +1,67 @@
|
|||
use std::{future::Future, rc::Rc};
|
||||
|
||||
use crate::{spawn_local, ReadSignal, Scope, WriteSignal};
|
||||
|
||||
pub struct Resource<S, T, Fu>
|
||||
where
|
||||
S: 'static,
|
||||
T: 'static,
|
||||
Fu: Future<Output = T>,
|
||||
{
|
||||
pub data: ReadSignal<Option<T>>,
|
||||
set_data: WriteSignal<Option<T>>,
|
||||
source: ReadSignal<S>,
|
||||
fetcher: Rc<dyn Fn(&S) -> Fu>,
|
||||
}
|
||||
|
||||
impl<S, T, Fu> Resource<S, T, Fu>
|
||||
where
|
||||
S: 'static,
|
||||
T: 'static,
|
||||
Fu: Future<Output = T> + 'static,
|
||||
{
|
||||
pub fn new(cx: Scope, source: ReadSignal<S>, fetcher: impl Fn(&S) -> Fu + 'static) -> Self {
|
||||
// create signals to handle response
|
||||
let (data, set_data) = cx.create_signal_owned(None);
|
||||
let fetcher = Rc::new(fetcher);
|
||||
|
||||
cx.create_effect({
|
||||
let source = source.clone();
|
||||
let set_data = set_data.clone();
|
||||
let fetcher = Rc::clone(&fetcher);
|
||||
move || {
|
||||
let fut = (fetcher)(&source.get());
|
||||
|
||||
let set_data = set_data.clone();
|
||||
spawn_local(async move {
|
||||
let res = fut.await;
|
||||
set_data.update(move |data| *data = Some(res));
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// return the Resource synchronously
|
||||
Self {
|
||||
data,
|
||||
set_data,
|
||||
source,
|
||||
fetcher,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn refetch(&self) {
|
||||
let source = self.source.clone();
|
||||
let set_data = self.set_data.clone();
|
||||
let fetcher = Rc::clone(&self.fetcher);
|
||||
let fut = (fetcher)(&source.get());
|
||||
|
||||
spawn_local(async move {
|
||||
let res = fut.await;
|
||||
set_data.update(move |data| *data = Some(res));
|
||||
});
|
||||
}
|
||||
|
||||
pub fn mutate(&self, update_fn: impl FnOnce(&mut Option<T>)) {
|
||||
self.set_data.update(update_fn);
|
||||
}
|
||||
}
|
|
@ -1,7 +1,8 @@
|
|||
use crate::scope_arena::ScopeArena;
|
||||
use crate::{EffectInner, SignalState};
|
||||
use crate::{EffectInner, Resource, SignalState};
|
||||
|
||||
use super::{root_context::RootContext, Effect, ReadSignal, WriteSignal};
|
||||
use std::future::Future;
|
||||
use std::{
|
||||
any::{Any, TypeId},
|
||||
cell::RefCell,
|
||||
|
@ -84,6 +85,19 @@ impl<'a, 'b> BoundedScope<'a, 'b> {
|
|||
self.inner.use_context()
|
||||
}
|
||||
|
||||
pub fn create_resource<S, T, Fu>(
|
||||
self,
|
||||
source: ReadSignal<S>,
|
||||
fetcher: impl Fn(&S) -> Fu + 'static,
|
||||
) -> &'a Resource<S, T, Fu>
|
||||
where
|
||||
S: 'static,
|
||||
T: 'static,
|
||||
Fu: Future<Output = T> + 'static,
|
||||
{
|
||||
self.create_ref(Resource::new(self, source, fetcher))
|
||||
}
|
||||
|
||||
pub fn child_scope<F>(self, f: F) -> ScopeDisposer<'a>
|
||||
where
|
||||
F: for<'child_lifetime> FnOnce(BoundedScope<'child_lifetime, 'a>),
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
use std::{
|
||||
borrow::Borrow,
|
||||
cell::{Ref, RefCell},
|
||||
collections::HashSet,
|
||||
rc::{Rc, Weak},
|
||||
|
@ -76,6 +77,12 @@ pub struct ReadSignalRef<'a, T> {
|
|||
guard: Ref<'a, T>,
|
||||
}
|
||||
|
||||
impl<'a, T> ReadSignalRef<'a, T> {
|
||||
pub fn guard(&self) -> &Ref<'a, T> {
|
||||
&self.guard
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T> Deref for ReadSignalRef<'a, T> {
|
||||
type Target = T;
|
||||
|
||||
|
|
18
leptos_reactive/src/spawn.rs
Normal file
18
leptos_reactive/src/spawn.rs
Normal file
|
@ -0,0 +1,18 @@
|
|||
use std::future::Future;
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub fn spawn_local<F>(fut: F)
|
||||
where
|
||||
F: Future<Output = ()> + 'static,
|
||||
{
|
||||
wasm_bindgen_futures::spawn_local(fut)
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub fn spawn_local<F>(_fut: F)
|
||||
where
|
||||
F: Future<Output = ()> + 'static,
|
||||
{
|
||||
// noop for now; useful for ignoring any async tasks on the server side
|
||||
// could be replaced with a Tokio dependency
|
||||
}
|
Loading…
Reference in a new issue