yew/functional/hooks/use_prepared_state/
feat_hydration.rs1use std::marker::PhantomData;
4use std::rc::Rc;
5
6use serde::de::DeserializeOwned;
7use serde::Serialize;
8use wasm_bindgen::JsValue;
9
10use super::PreparedStateBase;
11use crate::functional::{use_state, Hook, HookContext};
12use crate::platform::spawn_local;
13use crate::suspense::{Suspension, SuspensionResult};
14
15#[cfg(all(
16 target_arch = "wasm32",
17 not(target_os = "wasi"),
18 not(feature = "not_browser_env")
19))]
20async fn decode_base64(s: &str) -> Result<Vec<u8>, JsValue> {
21 use gloo::utils::window;
22 use js_sys::Uint8Array;
23 use wasm_bindgen::JsCast;
24 use wasm_bindgen_futures::JsFuture;
25
26 let fetch_promise = window().fetch_with_str(s);
27
28 let content_promise = JsFuture::from(fetch_promise)
29 .await
30 .and_then(|m| m.dyn_into::<web_sys::Response>())
31 .and_then(|m| m.array_buffer())?;
32
33 let content_array = JsFuture::from(content_promise)
34 .await
35 .as_ref()
36 .map(Uint8Array::new)?;
37
38 Ok(content_array.to_vec())
39}
40
41#[cfg(any(
42 not(target_arch = "wasm32"),
43 target_os = "wasi",
44 feature = "not_browser_env"
45))]
46async fn decode_base64(_s: &str) -> Result<Vec<u8>, JsValue> {
47 unreachable!("this function is not callable under non-wasm targets!");
48}
49
50#[doc(hidden)]
51pub fn use_prepared_state<T, D>(deps: D) -> impl Hook<Output = SuspensionResult<Option<Rc<T>>>>
52where
53 D: Serialize + DeserializeOwned + PartialEq + 'static,
54 T: Serialize + DeserializeOwned + 'static,
55{
56 struct HookProvider<T, D>
57 where
58 D: Serialize + DeserializeOwned + PartialEq + 'static,
59 T: Serialize + DeserializeOwned + 'static,
60 {
61 _marker: PhantomData<T>,
62 deps: D,
63 }
64
65 impl<T, D> Hook for HookProvider<T, D>
66 where
67 D: Serialize + DeserializeOwned + PartialEq + 'static,
68 T: Serialize + DeserializeOwned + 'static,
69 {
70 type Output = SuspensionResult<Option<Rc<T>>>;
71
72 fn run(self, ctx: &mut HookContext) -> Self::Output {
73 let data = use_state(|| {
74 let (s, handle) = Suspension::new();
75 (
76 SuspensionResult::<(Option<Rc<T>>, Option<Rc<D>>)>::Err(s),
77 Some(handle),
78 )
79 })
80 .run(ctx);
81
82 let state = {
83 let data = data.clone();
84 ctx.next_prepared_state(move |_re_render, buf| -> PreparedStateBase<T, D> {
85 if let Some(buf) = buf {
86 let buf = format!("data:application/octet-binary;base64,{buf}");
87
88 spawn_local(async move {
89 let buf = decode_base64(&buf)
90 .await
91 .expect("failed to deserialize state");
92
93 let (state, deps) =
94 bincode::deserialize::<(Option<T>, Option<D>)>(&buf)
95 .map(|(state, deps)| (state.map(Rc::new), deps.map(Rc::new)))
96 .expect("failed to deserialize state");
97
98 data.set((Ok((state, deps)), None));
99 });
100 }
101
102 PreparedStateBase {
103 #[cfg(feature = "ssr")]
104 state: None,
105 #[cfg(feature = "ssr")]
106 deps: None,
107
108 has_buf: buf.is_some(),
109 _marker: PhantomData,
110 }
111 })
112 };
113
114 if state.has_buf {
115 let (data, deps) = data.0.clone()?;
116
117 if deps.as_deref() == Some(&self.deps) {
118 return Ok(data);
119 }
120 }
121
122 Ok(None)
123 }
124 }
125
126 HookProvider::<T, D> {
127 _marker: PhantomData,
128 deps,
129 }
130}