This is unreleased documentation for Yew Next version.
For up-to-date documentation, see the latest version on docs.rs.

yew/functional/hooks/use_prepared_state/
feat_hydration.rs

1//! The client-side rendering variant. This is used for client side rendering.
2
3use 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::serde::decode_from_slice::<(Option<T>, Option<D>), _>(
95                                    &buf,
96                                    bincode::config::standard(),
97                                )
98                                .map(|((state, deps), consumed)| {
99                                    ((state.map(Rc::new), deps.map(Rc::new)), consumed)
100                                })
101                                .expect("failed to deserialize state");
102
103                            data.set((Ok((state, deps)), None));
104                        });
105                    }
106
107                    PreparedStateBase {
108                        #[cfg(feature = "ssr")]
109                        state: None,
110                        #[cfg(feature = "ssr")]
111                        deps: None,
112
113                        has_buf: buf.is_some(),
114                        _marker: PhantomData,
115                    }
116                })
117            };
118
119            if state.has_buf {
120                let (data, deps) = data.0.clone()?;
121
122                if deps.as_deref() == Some(&self.deps) {
123                    return Ok(data);
124                }
125            }
126
127            Ok(None)
128        }
129    }
130
131    HookProvider::<T, D> {
132        _marker: PhantomData,
133        deps,
134    }
135}