dioxus_fullstack_core/
server_future.rs

1use dioxus_core::{suspend, use_hook, RenderError};
2use dioxus_hooks::*;
3use dioxus_signals::ReadableExt;
4use serde::{de::DeserializeOwned, Serialize};
5use std::future::Future;
6
7/// Runs a future and returns a result with a resource if the future is finished or a suspended error if it is still running. The future may run on the server *during hydration*.
8/// - When compiled as server, the closure's future is ran to completion and the resulting data is serialized on the server and sent to the client.
9/// - When compiled as web client, the data is deserialized from the server if already available, otherwise runs on the client. Data is usually only available if this hook exists in a component during hydration.
10/// - When otherwise compiled, the closure is run directly with no serialization.
11///
12/// On the server, this will wait until the future is resolved before continuing to render. Thus, this blocks other subsequent server hooks. The result is cached.
13///
14///
15/// <div class="warning">
16///
17/// Unlike [`use_resource`] dependencies are only tracked inside the function that spawns the async block, not the async block itself.
18///
19/// ```rust, no_run
20/// # use dioxus::prelude::*;
21/// // ❌ The future inside of use_server_future is not reactive
22/// let id = use_signal(|| 0);
23/// use_server_future(move || {
24///     async move {
25///          // But the future is not reactive which means that the future will not subscribe to any reads here
26///          println!("{id}");
27///     }
28/// });
29/// // ✅ The closure that creates the future for use_server_future is reactive
30/// let id = use_signal(|| 0);
31/// use_server_future(move || {
32///     // The closure itself is reactive which means the future will subscribe to any signals you read here
33///     let cloned_id = id();
34///     async move {
35///          // But the future is not reactive which means that the future will not subscribe to any reads here
36///          println!("{cloned_id}");
37///     }
38/// });
39/// ```
40///
41/// </div>
42///
43/// # Example
44///
45/// ```rust, no_run
46/// # use dioxus::prelude::*;
47/// # async fn fetch_article(id: u32) -> String { unimplemented!() }
48/// use dioxus::prelude::*;
49///
50/// fn App() -> Element {
51///     let mut article_id = use_signal(|| 0);
52///     // `use_server_future` will spawn a task that runs on the server and serializes the result to send to the client.
53///     // The future will rerun any time the
54///     // Since we bubble up the suspense with `?`, the server will wait for the future to resolve before rendering
55///     let article = use_server_future(move || fetch_article(article_id()))?;
56///
57///     rsx! {
58///         "{article().unwrap()}"
59///     }
60/// }
61/// ```
62#[track_caller]
63pub fn use_server_future<T, F>(
64    mut future: impl FnMut() -> F + 'static,
65) -> Result<Resource<T>, RenderError>
66where
67    T: Serialize + DeserializeOwned + 'static,
68    F: Future<Output = T> + 'static,
69{
70    let serialize_context = use_hook(crate::serialize_context);
71
72    // We always create a storage entry, even if the data isn't ready yet to make it possible to deserialize pending server futures on the client
73    #[allow(unused)]
74    let storage_entry: crate::SerializeContextEntry<T> =
75        use_hook(|| serialize_context.create_entry());
76
77    #[cfg(feature = "server")]
78    let caller = std::panic::Location::caller();
79
80    // If this is the first run and we are on the web client, the data might be cached
81    #[cfg(feature = "web")]
82    let initial_web_result =
83        use_hook(|| std::rc::Rc::new(std::cell::RefCell::new(Some(storage_entry.get()))));
84
85    let resource = use_resource(move || {
86        #[cfg(feature = "server")]
87        let storage_entry = storage_entry.clone();
88
89        let user_fut = future();
90
91        #[cfg(feature = "web")]
92        let initial_web_result = initial_web_result.clone();
93
94        #[allow(clippy::let_and_return)]
95        async move {
96            // If this is the first run and we are on the web client, the data might be cached
97            #[cfg(feature = "web")]
98            match initial_web_result.take() {
99                // The data was deserialized successfully from the server
100                Some(Ok(o)) => return o,
101
102                // The data is still pending from the server. Don't try to resolve it on the client
103                Some(Err(crate::TakeDataError::DataPending)) => std::future::pending::<()>().await,
104
105                // The data was not available on the server, rerun the future
106                Some(Err(_)) => {}
107
108                // This isn't the first run, so we don't need do anything
109                None => {}
110            }
111
112            // Otherwise just run the future itself
113            let out = user_fut.await;
114
115            // If this is the first run and we are on the server, cache the data in the slot we reserved for it
116            #[cfg(feature = "server")]
117            storage_entry.insert(&out, caller);
118
119            out
120        }
121    });
122
123    // On the first run, force this task to be polled right away in case its value is ready
124    use_hook(|| {
125        let _ = resource.task().poll_now();
126    });
127
128    // Suspend if the value isn't ready
129    if resource.state().cloned() == UseResourceState::Pending {
130        let task = resource.task();
131        if !task.paused() {
132            return Err(suspend(task).unwrap_err());
133        }
134    }
135
136    Ok(resource)
137}
138
139// use dioxus_core::{suspend, use_hook, RenderError};
140// use dioxus_hooks::*;
141// use dioxus_signals::ReadableExt;
142// use serde::{de::DeserializeOwned, Serialize};
143// use std::future::Future;
144
145// /// Runs a future with a manual list of dependencies and returns a resource with the result if the future is finished or a suspended error if it is still running.
146// ///
147// ///
148// /// On the server, this will wait until the future is resolved before continuing to render. When the future is resolved, the result will be serialized into the page and hydrated on the client without rerunning the future.
149// ///
150// ///
151// /// <div class="warning">
152// ///
153// /// Unlike [`use_resource`] dependencies are only tracked inside the function that spawns the async block, not the async block itself.
154// ///
155// /// ```rust, no_run
156// /// # use dioxus::prelude::*;
157// /// // ❌ The future inside of use_server_future is not reactive
158// /// let id = use_signal(|| 0);
159// /// use_server_future(move || {
160// ///     async move {
161// ///          // But the future is not reactive which means that the future will not subscribe to any reads here
162// ///          println!("{id}");
163// ///     }
164// /// });
165// /// // ✅ The closure that creates the future for use_server_future is reactive
166// /// let id = use_signal(|| 0);
167// /// use_server_future(move || {
168// ///     // The closure itself is reactive which means the future will subscribe to any signals you read here
169// ///     let cloned_id = id();
170// ///     async move {
171// ///          // But the future is not reactive which means that the future will not subscribe to any reads here
172// ///          println!("{cloned_id}");
173// ///     }
174// /// });
175// /// ```
176// ///
177// /// </div>
178// ///
179// /// # Example
180// ///
181// /// ```rust, no_run
182// /// # use dioxus::prelude::*;
183// /// # async fn fetch_article(id: u32) -> String { unimplemented!() }
184// /// use dioxus::prelude::*;
185// ///
186// /// fn App() -> Element {
187// ///     let mut article_id = use_signal(|| 0);
188// ///     // `use_server_future` will spawn a task that runs on the server and serializes the result to send to the client.
189// ///     // The future will rerun any time the
190// ///     // Since we bubble up the suspense with `?`, the server will wait for the future to resolve before rendering
191// ///     let article = use_server_future(move || fetch_article(article_id()))?;
192// ///
193// ///     rsx! {
194// ///         "{article().unwrap()}"
195// ///     }
196// /// }
197// /// ```
198// #[must_use = "Consider using `cx.spawn` to run a future without reading its value"]
199// #[track_caller]
200// pub fn use_server_future<T, F>(
201//     mut future: impl FnMut() -> F + 'static,
202// ) -> Result<Resource<T>, RenderError>
203// where
204//     T: Serialize + DeserializeOwned + 'static,
205//     F: Future<Output = T> + 'static,
206// {
207//     let serialize_context = use_hook(crate::serialize_context);
208
209//     // We always create a storage entry, even if the data isn't ready yet to make it possible to deserialize pending server futures on the client
210//     #[allow(unused)]
211//     let storage_entry: crate::SerializeContextEntry<T> =
212//         use_hook(|| serialize_context.create_entry());
213
214//     #[cfg(feature = "server")]
215//     let caller = std::panic::Location::caller();
216
217//     // If this is the first run and we are on the web client, the data might be cached
218//     #[cfg(feature = "web")]
219//     let initial_web_result =
220//         use_hook(|| std::rc::Rc::new(std::cell::RefCell::new(Some(storage_entry.get()))));
221
222//     let resource = use_resource(move || {
223//         #[cfg(feature = "server")]
224//         let storage_entry = storage_entry.clone();
225
226//         let user_fut = future();
227
228//         #[cfg(feature = "web")]
229//         let initial_web_result = initial_web_result.clone();
230
231//         #[allow(clippy::let_and_return)]
232//         async move {
233//             // If this is the first run and we are on the web client, the data might be cached
234//             #[cfg(feature = "web")]
235//             match initial_web_result.take() {
236//                 // The data was deserialized successfully from the server
237//                 Some(Ok(o)) => return o,
238
239//                 // The data is still pending from the server. Don't try to resolve it on the client
240//                 Some(Err(crate::TakeDataError::DataPending)) => std::future::pending::<()>().await,
241
242//                 // The data was not available on the server, rerun the future
243//                 Some(Err(_)) => {}
244
245//                 // This isn't the first run, so we don't need do anything
246//                 None => {}
247//             }
248
249//             // Otherwise just run the future itself
250//             let out = user_fut.await;
251
252//             // If this is the first run and we are on the server, cache the data in the slot we reserved for it
253//             #[cfg(feature = "server")]
254//             storage_entry.insert(&out, caller);
255
256//             out
257//         }
258//     });
259
260//     // On the first run, force this task to be polled right away in case its value is ready
261//     use_hook(|| {
262//         let _ = resource.task().poll_now();
263//     });
264
265//     // Suspend if the value isn't ready
266//     if resource.state().cloned() == UseResourceState::Pending {
267//         let task = resource.task();
268//         if !task.paused() {
269//             return Err(suspend(task).unwrap_err());
270//         }
271//     }
272
273//     Ok(resource)
274// }
275
276// // use dioxus_core::{suspend, use_hook, RenderError};
277// // use dioxus_hooks::*;
278// // use dioxus_signals::ReadableExt;
279// // use std::future::Future;
280// // // use crate::Transportable;
281
282// // /// Runs a future with a manual list of dependencies and returns a resource with the result if the future is finished or a suspended error if it is still running.
283// // ///
284// // ///
285// // /// On the server, this will wait until the future is resolved before continuing to render. When the future is resolved, the result will be serialized into the page and hydrated on the client without rerunning the future.
286// // ///
287// // ///
288// // /// <div class="warning">
289// // ///
290// // /// Unlike [`use_resource`] dependencies are only tracked inside the function that spawns the async block, not the async block itself.
291// // ///
292// // /// ```rust, no_run
293// // /// # use dioxus::prelude::*;
294// // /// // ❌ The future inside of use_server_future is not reactive
295// // /// let id = use_signal(|| 0);
296// // /// use_server_future(move || {
297// // ///     async move {
298// // ///          // But the future is not reactive which means that the future will not subscribe to any reads here
299// // ///          println!("{id}");
300// // ///     }
301// // /// });
302// // /// // ✅ The closure that creates the future for use_server_future is reactive
303// // /// let id = use_signal(|| 0);
304// // /// use_server_future(move || {
305// // ///     // The closure itself is reactive which means the future will subscribe to any signals you read here
306// // ///     let cloned_id = id();
307// // ///     async move {
308// // ///          // But the future is not reactive which means that the future will not subscribe to any reads here
309// // ///          println!("{cloned_id}");
310// // ///     }
311// // /// });
312// // /// ```
313// // ///
314// // /// </div>
315// // ///
316// // /// # Example
317// // ///
318// // /// ```rust, no_run
319// // /// # use dioxus::prelude::*;
320// // /// # async fn fetch_article(id: u32) -> String { unimplemented!() }
321// // /// use dioxus::prelude::*;
322// // ///
323// // /// fn App() -> Element {
324// // ///     let mut article_id = use_signal(|| 0);
325// // ///     // `use_server_future` will spawn a task that runs on the server and serializes the result to send to the client.
326// // ///     // The future will rerun any time the
327// // ///     // Since we bubble up the suspense with `?`, the server will wait for the future to resolve before rendering
328// // ///     let article = use_server_future(move || fetch_article(article_id()))?;
329// // ///
330// // ///     rsx! {
331// // ///         "{article().unwrap()}"
332// // ///     }
333// // /// }
334// // /// ```
335// // #[must_use = "Consider using `cx.spawn` to run a future without reading its value"]
336// // #[track_caller]
337// // pub fn use_server_future<T, F, M>(
338// //     future: impl FnMut() -> F + 'static,
339// // ) -> Result<Resource<T>, RenderError>
340// // where
341// //     F: Future<Output = T> + 'static,
342// //     T: Transportable<M>,
343// //     M: 'static,
344// // {
345// //     let resource = use_server_future_unsuspended(future);
346
347// //     // Suspend if the value isn't ready
348// //     if resource.state().cloned() == UseResourceState::Pending {
349// //         let task = resource.task();
350// //         if !task.paused() {
351// //             return Err(suspend(task).unwrap_err());
352// //         }
353// //     }
354
355// //     Ok(resource)
356// // }
357
358// // /// Like [`use_server_future`] but does not suspend if the future is still running.
359// // ///
360// // /// You need to manage suspending yourself by checking the resource state.
361// // pub fn use_server_future_unsuspended<T, F, M>(
362// //     mut future: impl FnMut() -> F + 'static,
363// // ) -> Resource<T>
364// // where
365// //     F: Future<Output = T> + 'static,
366// //     T: Transportable<M>,
367// //     M: 'static,
368// // {
369// //     let serialize_context = use_hook(crate::transport::serialize_context);
370
371// //     // We always create a storage entry, even if the data isn't ready yet to make it possible to deserialize pending server futures on the client
372// //     #[allow(unused)]
373// //     let storage_entry: crate::transport::SerializeContextEntry<T> =
374// //         use_hook(|| serialize_context.create_entry());
375
376// //     #[cfg(feature = "server")]
377// //     let caller = std::panic::Location::caller();
378
379// //     // If this is the first run and we are on the web client, the data might be cached
380// //     #[cfg(feature = "web")]
381// //     let initial_web_result =
382// //         use_hook(|| std::rc::Rc::new(std::cell::RefCell::new(Some(storage_entry.get()))));
383
384// //     let resource = use_resource(move || {
385// //         #[cfg(feature = "server")]
386// //         let storage_entry = storage_entry.clone();
387
388// //         let user_fut = future();
389
390// //         #[cfg(feature = "web")]
391// //         let initial_web_result = initial_web_result.clone();
392
393// //         #[allow(clippy::let_and_return)]
394// //         async move {
395// //             // If this is the first run and we are on the web client, the data might be cached
396// //             #[cfg(feature = "web")]
397// //             match initial_web_result.take() {
398// //                 // The data was deserialized successfully from the server
399// //                 Some(Ok(o)) => return o,
400
401// //                 // The data is still pending from the server. Don't try to resolve it on the client
402// //                 Some(Err(crate::transport::TakeDataError::DataPending)) => {
403// //                     std::future::pending::<()>().await
404// //                 }
405
406// //                 // The data was not available on the server, rerun the future
407// //                 Some(Err(_)) => {}
408
409// //                 // This isn't the first run, so we don't need do anything
410// //                 None => {}
411// //             }
412
413// //             // Otherwise just run the future itself
414// //             let out = user_fut.await;
415
416// //             // If this is the first run and we are on the server, cache the data in the slot we reserved for it
417// //             #[cfg(feature = "server")]
418// //             storage_entry.insert(&out, caller);
419
420// //             out
421// //         }
422// //     });
423
424// //     // On the first run, force this task to be polled right away in case its value is ready
425// //     use_hook(|| {
426// //         let _ = resource.task().poll_now();
427// //     });
428
429// //     resource
430// // }