Loader calls for subsequent page navigations in hydration mode

This commit is contained in:
Greg Johnston 2022-10-05 20:37:29 -04:00
parent 93b87575c4
commit 12db14cb40
4 changed files with 100 additions and 28 deletions

View file

@ -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<Box<dyn Future<Output = T>>>);
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()))
})
}
}

View file

@ -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"

View file

@ -123,7 +123,7 @@ impl RouteContext {
resolve_path(&self.inner.base_path, to, Some(&self.inner.path))
}
pub(crate) fn child(&self) -> Option<RouteContext> {
pub fn child(&self) -> Option<RouteContext> {
(self.inner.child)()
}

View file

@ -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::<T>().cloned().unwrap_or_else(|| {
debug_warn!(
"use_loader() could not downcast to {:?}",
std::any::type_name::<T>(),
);
panic!()
})
any_data
.as_any()
.downcast_ref::<T>()
.cloned()
.unwrap_or_else(|| {
debug_warn!(
"use_loader() could not downcast to {:?}",
std::any::type_name::<T>(),
);
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<String>;
fn as_any(&self) -> &dyn Any;
}
impl<T> AnySerialize for T
where
T: Any + Serialize + 'static,
{
fn serialize(&self) -> Option<String> {
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<dyn Fn(Scope, ParamsMap, Url) -> PinnedFuture<Box<dyn Any>>>,
pub(crate) data: Rc<dyn Fn(Scope, ParamsMap, Url) -> PinnedFuture<Box<dyn AnySerialize>>>,
}
impl Loader {
#[cfg(not(feature = "hydrate"))]
pub fn call(&self, cx: Scope) -> impl Future<Output = Box<dyn AnySerialize>> {
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<F, Fu, T> From<F> for Loader
where
F: Fn(Scope, ParamsMap, Url) -> Fu + 'static,
Fu: Future<Output = T> + '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<dyn Any> })
as PinnedFuture<Box<dyn Any>>
Box::pin(async move { Box::new(res.await) as Box<dyn AnySerialize> })
as PinnedFuture<Box<dyn AnySerialize>>
};
Self {