mirror of
https://github.com/leptos-rs/leptos
synced 2024-09-20 14:32:00 +00:00
Loader calls for subsequent page navigations in hydration mode
This commit is contained in:
parent
93b87575c4
commit
12db14cb40
4 changed files with 100 additions and 28 deletions
|
@ -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()))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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)()
|
||||
}
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
Loading…
Reference in a new issue