use leptos::prelude::*;
use serde::{Serialize, de::DeserializeOwned};
use std::future::Future;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct HydratedSignal<T: 'static>(pub RwSignal<T>);
pub fn use_hydrate_signal<T, Fut>(
ssr_value: impl Fn() -> T + 'static,
fetcher: impl Fn() -> Fut + Send + Sync + 'static,
) -> (RwSignal<T>, Resource<T>)
where
T: Clone + Serialize + DeserializeOwned + Default + Send + Sync + 'static,
Fut: Future<Output = Result<T, ServerFnError>> + Send + 'static,
{
let resource = Resource::new(
|| (),
move |_| {
let f = fetcher();
async move { f.await.unwrap_or_default() }
},
);
let initial_val = ssr_value();
let signal = RwSignal::new(initial_val);
#[cfg(feature = "ssr")]
{
let _ = resource.get();
}
#[cfg(not(feature = "ssr"))]
{
Effect::new(move |_| {
if let Some(val) = resource.get() {
signal.set(val);
}
});
}
(signal, resource)
}
#[component]
pub fn Hydrate<T, Fut, ChildView>(
ssr_value: impl Fn() -> T + 'static,
fetcher: impl Fn() -> Fut + Send + Sync + 'static,
children: impl Fn(RwSignal<T>) -> ChildView + 'static,
) -> impl IntoView
where
T: Clone + Serialize + DeserializeOwned + Default + Send + Sync + 'static,
Fut: Future<Output = Result<T, ServerFnError>> + Send + 'static,
ChildView: IntoView + 'static,
{
let (signal, _) = use_hydrate_signal(ssr_value, fetcher);
children(signal)
}
#[component]
pub fn HydrateContext<T, Fut>(
ssr_value: impl Fn() -> T + 'static,
fetcher: impl Fn() -> Fut + Send + Sync + 'static,
children: Children,
) -> impl IntoView
where
T: Clone + Serialize + DeserializeOwned + Default + Send + Sync + 'static,
Fut: Future<Output = Result<T, ServerFnError>> + Send + 'static,
{
let (signal, resource) = use_hydrate_signal(ssr_value, fetcher);
provide_context(HydratedSignal(signal));
provide_context(resource);
children()
}
pub fn use_hydrated<T>() -> RwSignal<T>
where
T: Clone + Send + Sync + 'static,
{
use_context::<HydratedSignal<T>>().map(|s| s.0).expect(
"HydratedSignal not found. Did you wrap this part of the tree in <HydrateContext />?",
)
}
pub fn use_hydrated_resource<T>() -> Resource<T>
where
T: Clone + Send + Sync + 'static,
{
use_context::<Resource<T>>().expect(
"Hydrated Resource not found. Did you wrap this part of the tree in <HydrateContext />?",
)
}
#[cfg(test)]
mod tests {
use super::*;
use leptos::reactive::owner::Owner;
#[tokio::test]
async fn test_use_hydrate_signal_csr_init() {
let _ = any_spawner::Executor::init_tokio();
let owner = Owner::new_root(None);
owner.with(|| {
let (signal, _resource) = use_hydrate_signal(
|| 42,
|| async {
tokio::task::yield_now().await;
Ok::<i32, ServerFnError>(100)
},
);
assert_eq!(signal.get(), 42);
});
}
}