mirror of
https://github.com/leptos-rs/leptos
synced 2024-11-10 06:44:17 +00:00
Suspense
This commit is contained in:
parent
a8cc450939
commit
5612793f4e
14 changed files with 342 additions and 72 deletions
2
TODO.md
2
TODO.md
|
@ -1,6 +1,6 @@
|
|||
- [ ] Async
|
||||
- [x] Resource
|
||||
- [ ] Suspense
|
||||
- [x] Suspense
|
||||
- [ ] Transitions
|
||||
- [ ] Router
|
||||
- [ ] Docs (and clippy warning to insist on docs)
|
||||
|
|
|
@ -9,6 +9,8 @@ leptos = { path = "../../leptos" }
|
|||
reqwasm = "0.5.0"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
wee_alloc = "0.4"
|
||||
log = "0.4"
|
||||
console_log = "0.2"
|
||||
|
||||
[dev-dependencies]
|
||||
wasm-bindgen-test = "0.3.0"
|
||||
|
|
|
@ -3,5 +3,11 @@
|
|||
<head>
|
||||
<link data-trunk rel="rust" data-wasm-opt="z"/>
|
||||
</head>
|
||||
<style>
|
||||
img {
|
||||
max-width: 250px;
|
||||
height: auto;
|
||||
}
|
||||
</style>
|
||||
<body></body>
|
||||
</html>
|
|
@ -1,4 +1,3 @@
|
|||
use anyhow::Result;
|
||||
use leptos::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
|
@ -7,7 +6,7 @@ pub struct Cat {
|
|||
url: String,
|
||||
}
|
||||
|
||||
async fn fetch_cats(count: u32) -> Result<Vec<String>> {
|
||||
async fn fetch_cats(count: u32) -> Result<Vec<String>, ()> {
|
||||
if count > 0 {
|
||||
log!("fetching cats");
|
||||
let res = reqwasm::http::Request::get(&format!(
|
||||
|
@ -15,9 +14,11 @@ async fn fetch_cats(count: u32) -> Result<Vec<String>> {
|
|||
count
|
||||
))
|
||||
.send()
|
||||
.await?
|
||||
.await
|
||||
.map_err(|_| ())?
|
||||
.json::<Vec<Cat>>()
|
||||
.await?
|
||||
.await
|
||||
.map_err(|_| ())?
|
||||
.into_iter()
|
||||
.map(|cat| cat.url)
|
||||
.collect::<Vec<_>>();
|
||||
|
@ -29,16 +30,15 @@ async fn fetch_cats(count: u32) -> Result<Vec<String>> {
|
|||
}
|
||||
|
||||
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()));
|
||||
let (cat_count, set_cat_count) = cx.create_signal::<u32>(3);
|
||||
let cats = cx.create_ref(cx.create_resource(cat_count.clone(), |count| fetch_cats(*count)));
|
||||
|
||||
view! {
|
||||
<div>
|
||||
<label>
|
||||
"How many cats would you like?"
|
||||
<input type="number"
|
||||
value={move || cat_count.get().to_string()}
|
||||
on:input=move |ev| {
|
||||
let val = event_target_value(&ev).parse::<u32>().unwrap_or(0);
|
||||
log!("set_cat_count {val}");
|
||||
|
@ -46,21 +46,26 @@ pub fn fetch_example(cx: Scope) -> web_sys::Element {
|
|||
}
|
||||
/>
|
||||
</label>
|
||||
{move || match &*cats.data.get() {
|
||||
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>
|
||||
//<Suspense fallback={"Loading (Suspense Fallback)...".to_string()}>
|
||||
{move || match &*cats.read() {
|
||||
ResourceState::Idle => view! { <p>"(no data)"</p> },
|
||||
ResourceState::Pending { .. } => view! { <p>"Loading..."</p> },
|
||||
ResourceState::Ready { data: Err(_) } => view! { <pre>"Error"</pre> },
|
||||
ResourceState::Ready { data: Ok(cats) } => view! {
|
||||
<div>{
|
||||
cats.iter()
|
||||
.map(|src| {
|
||||
view! {
|
||||
<img src={src}/>
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
}</div>
|
||||
}
|
||||
}}
|
||||
//</Suspense>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,5 +5,7 @@ use leptos::*;
|
|||
static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;
|
||||
|
||||
pub fn main() {
|
||||
console_log::init_with_level(log::Level::Debug);
|
||||
|
||||
mount_to_body(fetch_example)
|
||||
}
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
mod for_component;
|
||||
mod map;
|
||||
mod suspense;
|
||||
|
||||
pub use for_component::*;
|
||||
pub use suspense::*;
|
||||
|
||||
pub trait Prop {
|
||||
type Builder;
|
||||
|
|
40
leptos_core/src/suspense.rs
Normal file
40
leptos_core/src/suspense.rs
Normal file
|
@ -0,0 +1,40 @@
|
|||
use crate as leptos;
|
||||
use leptos_dom::{Child, IntoChild};
|
||||
use leptos_macro::Props;
|
||||
use leptos_reactive::{Scope, SuspenseContext};
|
||||
|
||||
#[derive(Props)]
|
||||
pub struct SuspenseProps<F, C, G>
|
||||
where
|
||||
F: for<'a> IntoChild<'a> + Clone,
|
||||
C: for<'a> IntoChild<'a> + Clone,
|
||||
G: Fn() -> C,
|
||||
{
|
||||
fallback: F,
|
||||
children: G,
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
pub fn Suspense<'a, F, C, G>(cx: Scope<'a>, props: SuspenseProps<F, C, G>) -> impl Fn() -> Child<'a>
|
||||
where
|
||||
F: for<'b> IntoChild<'b> + Clone,
|
||||
C: for<'b> IntoChild<'b> + Clone,
|
||||
G: Fn() -> C,
|
||||
{
|
||||
let context = SuspenseContext::new(cx);
|
||||
|
||||
// provide this SuspenseContext to any resources below it
|
||||
cx.provide_context(context.clone());
|
||||
|
||||
leptos_dom::log!("point A");
|
||||
move || {
|
||||
if context.ready() {
|
||||
leptos_dom::log!("point B");
|
||||
|
||||
(props.children)().into_child(cx)
|
||||
} else {
|
||||
leptos_dom::log!("point C");
|
||||
props.fallback.clone().into_child(cx)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -5,6 +5,8 @@ edition = "2021"
|
|||
|
||||
[dependencies]
|
||||
bumpalo = "3"
|
||||
futures = "0.3"
|
||||
log = "0.4"
|
||||
|
||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||
wasm-bindgen-futures = "0.4"
|
|
@ -1,5 +1,6 @@
|
|||
use std::{
|
||||
cell::RefCell,
|
||||
fmt::Debug,
|
||||
hash::Hash,
|
||||
rc::{Rc, Weak},
|
||||
};
|
||||
|
@ -35,6 +36,8 @@ impl Effect {
|
|||
|
||||
impl EffectInner {
|
||||
pub(crate) fn execute(&self, for_stack: Weak<EffectInner>) {
|
||||
crate::debug_warn!("executing Effect: cleanup");
|
||||
|
||||
// clear previous dependencies
|
||||
// at this point, Effect dependencies have been added to Signal
|
||||
// and any Signal changes will call Effect dependency automatically
|
||||
|
@ -42,12 +45,23 @@ impl EffectInner {
|
|||
self.cleanup(upgraded);
|
||||
}
|
||||
|
||||
crate::debug_warn!("executing Effect: pushing to stack");
|
||||
|
||||
// add it to the Scope stack, which means any signals called
|
||||
// in the effect fn immediately below will add this Effect as a dependency
|
||||
self.stack.push(for_stack);
|
||||
|
||||
// actually run the effect, which will re-add Signal dependencies as they're called
|
||||
(self.f.borrow_mut())();
|
||||
crate::debug_warn!(
|
||||
"about to run effect — stack is {:?}",
|
||||
self.stack.stack.borrow()
|
||||
);
|
||||
match self.f.try_borrow_mut() {
|
||||
Ok(mut f) => (f)(),
|
||||
Err(e) => crate::debug_warn!("failed to BorrowMut while executing Effect: {}", e),
|
||||
}
|
||||
|
||||
crate::debug_warn!("executing Effect: popping from stack");
|
||||
|
||||
// pop it back off the stack
|
||||
self.stack.pop();
|
||||
|
@ -60,18 +74,40 @@ impl EffectInner {
|
|||
// this kind of dynamic dependency graph reconstruction may seem silly,
|
||||
// but is actually more efficient because it avoids resubscribing with Signals
|
||||
// if they are excluded by some kind of conditional logic within the Effect fn
|
||||
for dep in self.dependencies.borrow().iter() {
|
||||
match self.dependencies.try_borrow() {
|
||||
Ok(dependencies) => {
|
||||
for dep in dependencies.iter() {
|
||||
if let Some(dep) = dep.upgrade() {
|
||||
dep.unsubscribe(for_subscriber.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => crate::debug_warn!("failed to BorrowMut while unsubscribing: {}", e),
|
||||
}
|
||||
/* for dep in self.dependencies.borrow().iter() {
|
||||
if let Some(dep) = dep.upgrade() {
|
||||
dep.unsubscribe(for_subscriber.clone());
|
||||
}
|
||||
}
|
||||
} */
|
||||
// and clear all our dependencies on Signals; these will be built back up
|
||||
// by the Signals if/when they are called again
|
||||
self.dependencies.borrow_mut().clear();
|
||||
match self.dependencies.try_borrow_mut() {
|
||||
Ok(mut deps) => deps.clear(),
|
||||
Err(e) => crate::debug_warn!(
|
||||
"failed to BorrowMut while clearing dependencies from Effect: {}",
|
||||
e
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn add_dependency(&self, dep: Weak<dyn EffectDependency>) {
|
||||
self.dependencies.borrow_mut().push(dep);
|
||||
match self.dependencies.try_borrow_mut() {
|
||||
Ok(mut deps) => deps.push(dep),
|
||||
Err(e) => crate::debug_warn!(
|
||||
"failed to BorrowMut while clearing dependencies from Effect: {}",
|
||||
e
|
||||
),
|
||||
} // self.dependencies.borrow_mut().push(dep);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -94,3 +130,11 @@ impl Hash for EffectInner {
|
|||
std::ptr::hash(&self.dependencies, state);
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for EffectInner {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("EffectInner")
|
||||
.field("dependencies", &self.dependencies.borrow().len())
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@ mod scope;
|
|||
mod scope_arena;
|
||||
mod signal;
|
||||
mod spawn;
|
||||
mod suspense;
|
||||
|
||||
pub use effect::*;
|
||||
pub use resource::*;
|
||||
|
@ -18,6 +19,7 @@ pub use root_context::*;
|
|||
pub use scope::*;
|
||||
pub use signal::*;
|
||||
pub use spawn::*;
|
||||
pub use suspense::*;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
@ -76,3 +78,17 @@ mod tests {
|
|||
});
|
||||
}
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! debug_warn {
|
||||
($($x:tt)*) => {
|
||||
{
|
||||
#[cfg(debug_assertions)]
|
||||
{
|
||||
log::warn!($($x)*)
|
||||
}
|
||||
#[cfg(not(debug_assertions))]
|
||||
{ }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,67 +1,144 @@
|
|||
use std::{future::Future, rc::Rc};
|
||||
use std::{cell::RefCell, future::Future, rc::Rc};
|
||||
|
||||
use crate::{spawn_local, ReadSignal, Scope, WriteSignal};
|
||||
use futures::future::{AbortHandle, Abortable};
|
||||
|
||||
pub struct Resource<S, T, Fu>
|
||||
use crate::{spawn_local, ReadSignal, ReadSignalRef, Scope, SuspenseContext, WriteSignal};
|
||||
|
||||
pub enum ResourceState<T> {
|
||||
Idle,
|
||||
Pending { abort_handle: AbortHandle },
|
||||
Ready { data: T },
|
||||
}
|
||||
|
||||
pub struct Resource<'a, S, T, Fu>
|
||||
where
|
||||
S: 'static,
|
||||
S: 'static + Clone,
|
||||
T: 'static,
|
||||
Fu: Future<Output = T>,
|
||||
{
|
||||
pub data: ReadSignal<Option<T>>,
|
||||
set_data: WriteSignal<Option<T>>,
|
||||
state: ReadSignal<ResourceState<T>>,
|
||||
set_state: WriteSignal<ResourceState<T>>,
|
||||
source: ReadSignal<S>,
|
||||
source_memoized: Rc<RefCell<Option<S>>>,
|
||||
fetcher: Rc<dyn Fn(&S) -> Fu>,
|
||||
cx: Scope<'a>,
|
||||
}
|
||||
|
||||
impl<S, T, Fu> Resource<S, T, Fu>
|
||||
impl<'a, S, T, Fu> Clone for Resource<'a, S, T, Fu>
|
||||
where
|
||||
S: 'static,
|
||||
S: 'static + Clone + PartialEq,
|
||||
T: 'static,
|
||||
Fu: Future<Output = T>,
|
||||
{
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
state: self.state.clone(),
|
||||
set_state: self.set_state.clone(),
|
||||
source: self.source.clone(),
|
||||
source_memoized: Rc::clone(&self.source_memoized),
|
||||
fetcher: self.fetcher.clone(),
|
||||
cx: self.cx,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, S, T, Fu> Resource<'a, S, T, Fu>
|
||||
where
|
||||
S: 'static + Clone + PartialEq,
|
||||
T: 'static,
|
||||
Fu: Future<Output = T> + 'static,
|
||||
{
|
||||
pub fn new(cx: Scope, source: ReadSignal<S>, fetcher: impl Fn(&S) -> Fu + 'static) -> Self {
|
||||
pub fn new(cx: Scope<'a>, 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 (state, set_state) = cx.create_signal_owned(ResourceState::Idle);
|
||||
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,
|
||||
state,
|
||||
set_state,
|
||||
source,
|
||||
source_memoized: Default::default(),
|
||||
fetcher,
|
||||
cx,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn read(&self) -> ReadSignalRef<ResourceState<T>> {
|
||||
self.cx.create_effect(|| {
|
||||
// reactivity should only be driven by the source signal
|
||||
let source = self.source.get();
|
||||
let source_has_changed = {
|
||||
let mut prev_source = self.source_memoized.borrow_mut();
|
||||
let source_has_changed = prev_source.as_ref() != Some(&source);
|
||||
if source_has_changed {
|
||||
*prev_source = Some(source.clone());
|
||||
}
|
||||
source_has_changed
|
||||
};
|
||||
|
||||
match (source_has_changed, &*self.state.get_untracked()) {
|
||||
// if it's already loaded or is loading and source hasn't changed, return value when read
|
||||
(false, ResourceState::Ready { .. } | ResourceState::Pending { .. }) => {
|
||||
crate::debug_warn!("\nResource::read() called while ResourceState is Ready or Pending");
|
||||
}
|
||||
// if source has changed and we have a result, run fetch logic
|
||||
(true, ResourceState::Ready { .. }) => {
|
||||
crate::debug_warn!("\nResource::read() called while ResourceState::Ready but source has changed");
|
||||
self.refetch();
|
||||
}
|
||||
// if source has changed and it's loading, abort loading and run fetch logic
|
||||
(true, ResourceState::Pending { abort_handle}) => {
|
||||
crate::debug_warn!("\nResource::read() called while ResourceState::Pending but source has changed");
|
||||
abort_handle.abort();
|
||||
self.refetch();
|
||||
}
|
||||
// if this is the first read, run the logic
|
||||
(_, ResourceState::Idle) => {
|
||||
crate::debug_warn!("\nResource::read() called while ResourceState is idle");
|
||||
self.refetch();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
self.state.get()
|
||||
}
|
||||
|
||||
pub fn refetch(&self) {
|
||||
let suspense_cx = self.cx.use_context::<SuspenseContext>().cloned();
|
||||
if let Some(context) = &suspense_cx {
|
||||
context.increment();
|
||||
}
|
||||
|
||||
// actually await the future
|
||||
let source = self.source.clone();
|
||||
let set_data = self.set_data.clone();
|
||||
let set_state = self.set_state.clone();
|
||||
let fetcher = Rc::clone(&self.fetcher);
|
||||
let fut = (fetcher)(&source.get());
|
||||
|
||||
// get Future from factory function and make it abortable
|
||||
let fut = (fetcher)(&source.get_untracked());
|
||||
let (abort_handle, abort_registration) = AbortHandle::new_pair();
|
||||
let fut = Abortable::new(fut, abort_registration);
|
||||
|
||||
// set loading state
|
||||
set_state.update(|state| *state = ResourceState::Pending { abort_handle });
|
||||
|
||||
spawn_local(async move {
|
||||
let res = fut.await;
|
||||
set_data.update(move |data| *data = Some(res));
|
||||
let data = fut.await;
|
||||
|
||||
// if future has not been aborted, update state
|
||||
if let Ok(data) = data {
|
||||
set_state.update(move |state| *state = ResourceState::Ready { data });
|
||||
}
|
||||
|
||||
// if any case, decrement the read counter
|
||||
if let Some(suspense_cx) = &suspense_cx {
|
||||
suspense_cx.decrement();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
pub fn mutate(&self, update_fn: impl FnOnce(&mut Option<T>)) {
|
||||
self.set_data.update(update_fn);
|
||||
pub fn mutate(&self, update_fn: impl FnOnce(&mut ResourceState<T>)) {
|
||||
self.set_state.update(update_fn);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -89,13 +89,13 @@ impl<'a, 'b> BoundedScope<'a, 'b> {
|
|||
self,
|
||||
source: ReadSignal<S>,
|
||||
fetcher: impl Fn(&S) -> Fu + 'static,
|
||||
) -> &'a Resource<S, T, Fu>
|
||||
) -> Resource<'a, S, T, Fu>
|
||||
where
|
||||
S: 'static,
|
||||
S: 'static + Clone + PartialEq,
|
||||
T: 'static,
|
||||
Fu: Future<Output = T> + 'static,
|
||||
{
|
||||
self.create_ref(Resource::new(self, source, fetcher))
|
||||
Resource::new(self, source, fetcher)
|
||||
}
|
||||
|
||||
pub fn child_scope<F>(self, f: F) -> ScopeDisposer<'a>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
//use debug_cell::{Ref, RefCell};
|
||||
use std::{
|
||||
borrow::Borrow,
|
||||
cell::{Ref, RefCell},
|
||||
collections::HashSet,
|
||||
rc::{Rc, Weak},
|
||||
|
@ -61,13 +61,28 @@ impl<T: 'static> ReadSignal<T> {
|
|||
}
|
||||
|
||||
fn add_subscriber(&self, subscriber: Rc<EffectInner>) {
|
||||
self.inner.subscriptions.borrow_mut().insert(subscriber);
|
||||
match self.inner.subscriptions.try_borrow_mut() {
|
||||
Ok(mut subs) => {
|
||||
subs.insert(subscriber);
|
||||
}
|
||||
Err(e) => crate::debug_warn!(
|
||||
"failed to BorrowMut while adding subscriber to Signal: {}",
|
||||
e
|
||||
),
|
||||
}
|
||||
//self.inner.subscriptions.borrow_mut().insert(subscriber);
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> EffectDependency for SignalState<T> {
|
||||
fn unsubscribe(&self, effect: Rc<EffectInner>) {
|
||||
self.subscriptions.borrow_mut().remove(&effect);
|
||||
match self.subscriptions.try_borrow_mut() {
|
||||
Ok(mut subs) => {
|
||||
subs.remove(&effect);
|
||||
}
|
||||
Err(e) => crate::debug_warn!("failed to unsubscribing Signal from Effect: {}", e),
|
||||
}
|
||||
//self.subscriptions.borrow_mut().remove(&effect);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -145,11 +160,41 @@ where
|
|||
impl<T> WriteSignal<T> {
|
||||
pub fn update(&self, update_fn: impl FnOnce(&mut T)) {
|
||||
if let Some(inner) = self.inner.upgrade() {
|
||||
(update_fn)(&mut inner.value.borrow_mut());
|
||||
for subscription in inner.subscriptions.take().iter() {
|
||||
subscription.execute(Rc::downgrade(&subscription))
|
||||
match inner.value.try_borrow_mut() {
|
||||
Ok(mut value) => (update_fn)(&mut value),
|
||||
Err(e) => crate::debug_warn!("failed to BorrowMut while updating Signal: {}", e),
|
||||
}
|
||||
//(update_fn)(&mut inner.value.borrow_mut());
|
||||
|
||||
match inner.subscriptions.try_borrow() {
|
||||
Ok(subs) => {
|
||||
for subscription in subs.iter() {
|
||||
subscription.execute(Rc::downgrade(&subscription));
|
||||
}
|
||||
}
|
||||
Err(e) => crate::debug_warn!(
|
||||
"failed to BorrowMut while running dependencies for Signal: {}",
|
||||
e
|
||||
),
|
||||
}
|
||||
/* for subscription in inner.subscriptions.borrow_mut().iter() {
|
||||
subscription.execute(Rc::downgrade(&subscription));
|
||||
} */
|
||||
/* for subscription in inner.subscriptions.take().iter() {
|
||||
subscription.execute(Rc::downgrade(&subscription))
|
||||
} */
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_untracked(&self, update_fn: impl FnOnce(&mut T)) {
|
||||
if let Some(inner) = self.inner.upgrade() {
|
||||
match inner.value.try_borrow_mut() {
|
||||
Ok(mut value) => (update_fn)(&mut value),
|
||||
Err(e) => crate::debug_warn!(
|
||||
"failed to BorrowMut while calling WriteSignal::update_untracked {}",
|
||||
e
|
||||
),
|
||||
}
|
||||
} else {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
29
leptos_reactive/src/suspense.rs
Normal file
29
leptos_reactive/src/suspense.rs
Normal file
|
@ -0,0 +1,29 @@
|
|||
use crate::{ReadSignal, Scope, WriteSignal};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct SuspenseContext {
|
||||
pending_resources: ReadSignal<usize>,
|
||||
set_pending_resources: WriteSignal<usize>,
|
||||
}
|
||||
|
||||
impl SuspenseContext {
|
||||
pub fn new(cx: Scope) -> Self {
|
||||
let (pending_resources, set_pending_resources) = cx.create_signal_owned(0);
|
||||
Self {
|
||||
pending_resources,
|
||||
set_pending_resources,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn increment(&self) {
|
||||
self.set_pending_resources.update(|n| *n += 1);
|
||||
}
|
||||
|
||||
pub fn decrement(&self) {
|
||||
self.set_pending_resources.update(|n| *n -= 1);
|
||||
}
|
||||
|
||||
pub fn ready(&self) -> bool {
|
||||
*self.pending_resources.get() == 0
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue