From 12db14cb40a021ed1bb014a357725aca72809c6d Mon Sep 17 00:00:00 2001 From: Greg Johnston Date: Wed, 5 Oct 2022 20:37:29 -0400 Subject: [PATCH] Loader calls for subsequent page navigations in hydration mode --- leptos_reactive/src/resource.rs | 36 +++++++++---- router/Cargo.toml | 1 + router/src/components/route.rs | 2 +- router/src/data/loader.rs | 89 +++++++++++++++++++++++++++------ 4 files changed, 100 insertions(+), 28 deletions(-) diff --git a/leptos_reactive/src/resource.rs b/leptos_reactive/src/resource.rs index 5fec3320d..5b6049391 100644 --- a/leptos_reactive/src/resource.rs +++ b/leptos_reactive/src/resource.rs @@ -12,9 +12,9 @@ use std::{ use serde::{de::DeserializeOwned, Deserialize, Serialize}; use crate::{ - create_isomorphic_effect, create_memo, create_signal, queue_microtask, runtime::Runtime, - spawn::spawn_local, use_context, Memo, ReadSignal, Scope, ScopeId, SuspenseContext, - WriteSignal, + create_effect, create_isomorphic_effect, create_memo, create_signal, queue_microtask, + runtime::Runtime, spawn::spawn_local, use_context, Memo, ReadSignal, Scope, ScopeId, + SuspenseContext, WriteSignal, }; #[cfg(not(feature = "ssr"))] @@ -67,8 +67,6 @@ where let fetcher = Rc::new(move |s| Box::pin(fetcher(s)) as Pin>>); let source = create_memo(cx, move |_| source()); - // TODO hydration/streaming logic - let r = Rc::new(ResourceState { scope: cx, value, @@ -120,33 +118,46 @@ where { use wasm_bindgen::{JsCast, UnwrapThrowExt}; + log::debug!("[create_resource] load_resource in hydrate mode"); + if let Some(ref mut context) = *cx.runtime.shared_context.borrow_mut() { let resource_id = StreamingResourceId(cx.id, id); log::debug!( "(create_resource) resolved resources = {:#?}", context.resolved_resources ); + log::debug!( + "(create_resource) pending resources = {:#?}", + context.pending_resources + ); if let Some(data) = context.resolved_resources.remove(&resource_id) { log::debug!("(create_resource) resource already resolved from server"); + context.pending_resources.remove(&resource_id); // no longer pending r.resolved.set(true); - let decoded = base64::decode(&data).unwrap_throw(); - let res = bincode::deserialize(&decoded).unwrap_throw(); + //let decoded = base64::decode(&data).unwrap_throw(); + //let res = bincode::deserialize(&decoded).unwrap_throw(); + let res = serde_json::from_str(&data).unwrap_throw(); log::debug!("deserialized = {res:?}"); r.set_value.update(|n| *n = Some(res)); r.set_loading.update(|n| *n = false); + + // for reactivity + _ = r.source.get(); } else if context.pending_resources.remove(&resource_id) { log::debug!("(create_resource) resource pending from server"); r.set_loading.update(|n| *n = true); r.trigger.update(|n| *n += 1); let resolve = { + let runtime = cx.runtime; let resolved = r.resolved.clone(); let set_value = r.set_value; let set_loading = r.set_loading; move |res: String| { - let decoded = base64::decode(&res).unwrap_throw(); - let res = bincode::deserialize(&decoded).unwrap_throw(); + //let decoded = base64::decode(&res).unwrap_throw(); + //let res = bincode::deserialize(&decoded).unwrap_throw(); + let res = serde_json::from_str(&res).unwrap_throw(); resolved.set(true); set_value.update(|n| *n = res); set_loading.update(|n| *n = false); @@ -165,6 +176,9 @@ where &wasm_bindgen::JsValue::from_str(&id), resolve.as_ref().unchecked_ref(), ); + + // for reactivity + _ = r.source.get(); } else { log::debug!( "(create_resource) resource not found in hydration context, loading\n\n{:#?}", @@ -398,8 +412,8 @@ where let fut = (self.fetcher)(self.source.get()); Box::pin(async move { let res = fut.await; - //(id, serde_json::to_string(&res).unwrap()) - (id, base64::encode(&bincode::serialize(&res).unwrap())) + (id, serde_json::to_string(&res).unwrap()) + //(id, base64::encode(&bincode::serialize(&res).unwrap())) }) } } diff --git a/router/Cargo.toml b/router/Cargo.toml index 226f5b2c8..b10362de6 100644 --- a/router/Cargo.toml +++ b/router/Cargo.toml @@ -15,6 +15,7 @@ linear-map = "1" log = "0.4" regex = { version = "1", optional = true } serde = { version = "1", features = ["derive"] } +serde_json = { version = "1" } bincode = "1" url = { version = "2", optional = true } urlencoding = "2" diff --git a/router/src/components/route.rs b/router/src/components/route.rs index fa665da01..f4f5a1315 100644 --- a/router/src/components/route.rs +++ b/router/src/components/route.rs @@ -123,7 +123,7 @@ impl RouteContext { resolve_path(&self.inner.base_path, to, Some(&self.inner.path)) } - pub(crate) fn child(&self) -> Option { + pub fn child(&self) -> Option { (self.inner.child)() } diff --git a/router/src/data/loader.rs b/router/src/data/loader.rs index 3b4e6f764..3a5ecda3b 100644 --- a/router/src/data/loader.rs +++ b/router/src/data/loader.rs @@ -1,6 +1,8 @@ use std::{any::Any, fmt::Debug, future::Future, rc::Rc}; -use leptos_reactive::{create_resource, debug_warn, Memo, Resource, Scope}; +use leptos_reactive::{ + create_effect, create_memo, create_resource, debug_warn, Memo, Resource, Scope, +}; use serde::{de::DeserializeOwned, Deserialize, Serialize}; use crate::{use_location, use_params_map, use_route, Location, ParamsMap, PinnedFuture, Url}; @@ -38,13 +40,17 @@ where let loader = loader.clone(); async move { let any_data = (loader.clone())(cx, params, url).await; - any_data.downcast_ref::().cloned().unwrap_or_else(|| { - debug_warn!( - "use_loader() could not downcast to {:?}", - std::any::type_name::(), - ); - panic!() - }) + any_data + .as_any() + .downcast_ref::() + .cloned() + .unwrap_or_else(|| { + debug_warn!( + "use_loader() could not downcast to {:?}", + std::any::type_name::(), + ); + panic!() + }) } }, ) @@ -58,6 +64,8 @@ where { use wasm_bindgen::{JsCast, UnwrapThrowExt}; + use crate::use_query_map; + let route = use_route(cx); let params = use_params_map(cx); @@ -69,14 +77,24 @@ where hash: location.hash.get(), }; + log::debug!("[LOADER] hydrate call"); + + create_effect(cx, move |_| { + log::debug!("[LOADER] value of url() is {:#?}", url()); + }); + create_resource( cx, move || (params.get(), url()), move |(params, url)| async move { + log::debug!("[LOADER] calling loader; should fire whenever params or URL change"); + let route = use_route(cx); + let query = use_query_map(cx); + let mut opts = web_sys::RequestInit::new(); opts.method("GET"); - let url = route.path(); + let url = format!("{}{}", route.path(), query.get().to_query_string()); let request = web_sys::Request::new_with_str_and_init(&url, &opts).unwrap_throw(); request @@ -95,31 +113,70 @@ where .unwrap_throw() .as_string() .unwrap_throw(); - let decoded = window.atob(&text).unwrap_throw(); - bincode::deserialize(&decoded.as_bytes()).unwrap_throw() - //serde_json::from_str(&json.as_string().unwrap_throw()).unwrap_throw() + //let decoded = window.atob(&text).unwrap_throw(); + //bincode::deserialize(&decoded.as_bytes()).unwrap_throw() + //serde_json::from_str(&text.as_string().unwrap_throw()).unwrap_throw() + serde_json::from_str(&text).unwrap_throw() }, ) } +pub trait AnySerialize { + fn serialize(&self) -> Option; + + fn as_any(&self) -> &dyn Any; +} + +impl AnySerialize for T +where + T: Any + Serialize + 'static, +{ + fn serialize(&self) -> Option { + serde_json::to_string(&self).ok() + } + + fn as_any(&self) -> &dyn Any { + self + } +} + #[derive(Clone)] pub struct Loader { #[cfg(not(feature = "hydrate"))] - pub(crate) data: Rc PinnedFuture>>, + pub(crate) data: Rc PinnedFuture>>, +} + +impl Loader { + #[cfg(not(feature = "hydrate"))] + pub fn call(&self, cx: Scope) -> impl Future> { + let (params, url) = cx.untrack(|| { + let params = use_params_map(cx).get(); + let location = use_location(cx); + let url = Url { + origin: String::default(), + pathname: location.pathname.get(), + search: location.search.get(), + hash: location.hash.get(), + }; + (params, url) + }); + + (self.data)(cx, params, url) + } } impl From for Loader where F: Fn(Scope, ParamsMap, Url) -> Fu + 'static, Fu: Future + 'static, - T: Any + 'static, + T: Any + Serialize + 'static, { #[cfg(not(feature = "hydrate"))] fn from(f: F) -> Self { let wrapped_fn = move |cx, params, url| { let res = f(cx, params, url); - Box::pin(async move { Box::new(res.await) as Box }) - as PinnedFuture> + Box::pin(async move { Box::new(res.await) as Box }) + as PinnedFuture> }; Self {