diff --git a/packages/yew-link/src/lib.rs b/packages/yew-link/src/lib.rs index 2938d577826..c8a5657a970 100644 --- a/packages/yew-link/src/lib.rs +++ b/packages/yew-link/src/lib.rs @@ -93,6 +93,11 @@ impl fmt::Display for LinkError { } } +#[cfg(any(test, all(target_arch = "wasm32", not(feature = "ssr"))))] +fn should_cache_result(result: &Result>) -> bool { + !matches!(result, Err(LinkError::Internal(_))) +} + /// Handle returned by [`use_linked_state`]. /// /// Provides access to the resolved data, a [`refresh`](Self::refresh) @@ -313,6 +318,9 @@ type Cache = Rc>>; #[cfg(target_arch = "wasm32")] type InFlight = Rc>>; +#[cfg(target_arch = "wasm32")] +type Completed = Rc>>; + #[cfg(target_arch = "wasm32")] type Refreshing = Rc>>; @@ -322,6 +330,8 @@ struct LinkContextInner { #[cfg(target_arch = "wasm32")] in_flight: InFlight, #[cfg(target_arch = "wasm32")] + completed: Completed, + #[cfg(target_arch = "wasm32")] refreshing: Refreshing, endpoint: AttrValue, #[cfg(feature = "ssr")] @@ -334,6 +344,7 @@ impl PartialEq for LinkContextInner { #[cfg(target_arch = "wasm32")] { Rc::ptr_eq(&self.in_flight, &other.in_flight) + && Rc::ptr_eq(&self.completed, &other.completed) && Rc::ptr_eq(&self.refreshing, &other.refreshing) } #[cfg(not(target_arch = "wasm32"))] @@ -463,6 +474,8 @@ pub fn LinkProvider(props: &LinkProviderProps) -> Html { #[cfg(target_arch = "wasm32")] let in_flight: InFlight = (*use_ref(|| Rc::new(RefCell::new(HashMap::new())))).clone(); #[cfg(target_arch = "wasm32")] + let completed: Completed = (*use_ref(|| Rc::new(RefCell::new(HashMap::new())))).clone(); + #[cfg(target_arch = "wasm32")] let refreshing: Refreshing = (*use_ref(|| Rc::new(RefCell::new(HashSet::new())))).clone(); let ctx = LinkContextInner { @@ -470,6 +483,8 @@ pub fn LinkProvider(props: &LinkProviderProps) -> Html { #[cfg(target_arch = "wasm32")] in_flight, #[cfg(target_arch = "wasm32")] + completed, + #[cfg(target_arch = "wasm32")] refreshing, endpoint: props.endpoint.clone(), #[cfg(feature = "ssr")] @@ -572,6 +587,7 @@ pub fn use_linked_state(input: T::Input) -> SuspensionResult(input: T::Input) -> SuspensionResult(input: T::Input) -> SuspensionResult true, - Err(LinkError::Internal(_)) => false, - }; + let should_cache = should_cache_result(&result); if should_cache { if let Ok(json_val) = serde_json::to_value(&result) { - cache.borrow_mut().put(key, json_val); + cache.borrow_mut().put(key.clone(), json_val); } + completed.borrow_mut().remove(&key); } inner_force_update.force_update(); @@ -636,6 +651,16 @@ pub fn use_linked_state(input: T::Input) -> SuspensionResult>(completed_val) { + return Ok(LinkedStateHandle { + result: result.map(Rc::new), + refresh, + refreshing: is_refreshing, + }); + } + } + if let Some(sus) = link_ctx.in_flight.borrow().get(&key).cloned() { if !sus.resumed() { return Err(sus); @@ -660,14 +685,19 @@ pub fn use_linked_state(input: T::Input) -> SuspensionResult true, - Err(LinkError::Internal(_)) => false, - }; + let should_cache = should_cache_result(&result); if should_cache { if let Ok(json_val) = serde_json::to_value(&result) { - link_ctx.cache.borrow_mut().put(key, json_val); + link_ctx.cache.borrow_mut().put(key.clone(), json_val); } + link_ctx.completed.borrow_mut().remove(&key); + } else if let Ok(json_val) = serde_json::to_value(&result) { + // Keep transient initial failures outside the LRU cache so the completed + // suspension can surface the error without issuing another request. + link_ctx + .completed + .borrow_mut() + .insert(key.clone(), json_val); } } }); @@ -677,6 +707,22 @@ pub fn use_linked_state(input: T::Input) -> SuspensionResult> = Err(LinkError::Internal("offline".into())); + let resolved: Result<(), LinkError<()>> = Err(LinkError::Resolve(())); + let success: Result<(), LinkError<()>> = Ok(()); + + assert!(!should_cache_result(&internal)); + assert!(should_cache_result(&resolved)); + assert!(should_cache_result(&success)); + } +} + #[cfg(all(not(target_arch = "wasm32"), any(feature = "axum", feature = "actix")))] mod services;