use leptos::prelude::*;
use serde::{Serialize, de::DeserializeOwned};
use std::future::Future;
pub trait Hydratable: Clone + Serialize + DeserializeOwned + Default + Send + Sync + 'static {
fn initial() -> Self;
fn fetch() -> impl Future<Output = Result<Self, ServerFnError>> + Send + 'static;
}
#[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>, LocalResource<T>)
where
T: Clone + Serialize + DeserializeOwned + Default + Send + Sync + PartialEq + 'static,
Fut: Future<Output = Result<T, ServerFnError>> + Send + 'static,
{
let initial_val = ssr_value();
let signal = RwSignal::new(initial_val);
let resource = LocalResource::new(
move || {
let f = fetcher();
async move { f.await.unwrap_or_default() }
},
);
#[cfg(not(feature = "ssr"))]
{
leptos::task::spawn_local(async move {
let val = resource.await;
signal.set(val);
});
}
(signal, resource)
}
#[component]
pub fn HydrateState<T>(
#[prop(optional)] marker: std::marker::PhantomData<T>,
) -> impl IntoView
where
T: Hydratable + PartialEq,
{
let _ = marker;
view! {
<HydrateStateWith ssr_value=T::initial fetcher=T::fetch />
}
}
#[component]
pub fn HydrateContext<T>(
children: Children,
#[prop(optional)] marker: std::marker::PhantomData<T>,
) -> impl IntoView
where
T: Hydratable + PartialEq,
{
let _ = marker;
view! {
<HydrateContextWith ssr_value=T::initial fetcher=T::fetch>
{children()}
</HydrateContextWith>
}
}
#[component]
pub fn HydrateStateWith<T, Fut>(
ssr_value: impl Fn() -> T + 'static,
fetcher: impl Fn() -> Fut + Send + Sync + 'static,
) -> impl IntoView
where
T: Clone + Serialize + DeserializeOwned + Default + Send + Sync + PartialEq + '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);
}
#[component]
pub fn HydrateContextWith<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 + PartialEq + '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 <HydrateState />, <HydrateContext />, <HydrateStateWith />, or <HydrateContextWith />?",
)
}
pub fn try_use_hydrated<T>() -> Option<RwSignal<T>>
where
T: Clone + Send + Sync + 'static,
{
use_context::<HydratedSignal<T>>().map(|s| s.0)
}
pub fn use_hydrated_resource<T>() -> LocalResource<T>
where
T: Clone + Send + Sync + 'static,
{
use_context::<LocalResource<T>>().expect(
"Hydrated Resource not found. Did you wrap this part of the tree in <HydrateState />, <HydrateContext />, <HydrateStateWith />, or <HydrateContextWith />?",
)
}
pub fn try_use_hydrated_resource<T>() -> Option<LocalResource<T>>
where
T: Clone + Send + Sync + 'static,
{
use_context::<LocalResource<T>>()
}
#[cfg(test)]
mod tests;