Suspense

Suspense is a way to suspend component rendering whilst waiting a task to complete and a fallback (placeholder) UI is shown in the meanwhile.

It can be used to fetch data from server, wait for tasks to be completed by an agent, or perform other background asynchronous task.

Before suspense, data fetching usually happens after (Fetch-on-render) or before component rendering (Fetch-then-render).

Render-as-You-Fetch

Suspense enables a new approach that allows components to initiate data request during the rendering process. When a component initiates a data request, the rendering process will become suspended and a fallback UI will be shown until the request is completed.

The recommended way to use suspense is with hooks.

  1. use yew::prelude::*;
  2. #[function_component(Content)]
  3. fn content() -> HtmlResult {
  4. let user = use_user()?;
  5. Ok(html! {<div>{"Hello, "}{&user.name}</div>})
  6. }
  7. #[function_component(App)]
  8. fn app() -> Html {
  9. let fallback = html! {<div>{"Loading..."}</div>};
  10. html! {
  11. <Suspense {fallback}>
  12. <Content />
  13. </Suspense>
  14. }
  15. }

In the above example, the use_user hook will suspend the component rendering while user information is loading and a Loading... placeholder will be shown until user is loaded.

To define a hook that suspends a component rendering, it needs to return a SuspensionResult<T>. When the component needs to be suspended, the hook should return a Err(Suspension) and users should unwrap it with ? in which it will be converted into Html.

  1. use yew::prelude::*;
  2. use yew::suspense::{Suspension, SuspensionResult};
  3. struct User {
  4. name: String,
  5. }
  6. #[hook]
  7. fn use_user() -> SuspensionResult<User> {
  8. match load_user() {
  9. // If a user is loaded, then we return it as Ok(user).
  10. Some(m) => Ok(m),
  11. None => {
  12. // When user is still loading, then we create a `Suspension`
  13. // and call `SuspensionHandle::resume` when data loading
  14. // completes, the component will be re-rendered
  15. // automatically.
  16. let (s, handle) = Suspension::new();
  17. on_load_user_complete(move || {handle.resume();});
  18. Err(s)
  19. },
  20. }
  21. }

Complete Example

  1. use yew::prelude::*;
  2. use yew::suspense::{Suspension, SuspensionResult};
  3. #[derive(Debug)]
  4. struct User {
  5. name: String,
  6. }
  7. fn load_user() -> Option<User> {
  8. todo!() // implementation omitted.
  9. }
  10. fn on_load_user_complete<F: FnOnce()>(_fn: F) {
  11. todo!() // implementation omitted.
  12. }
  13. #[hook]
  14. fn use_user() -> SuspensionResult<User> {
  15. match load_user() {
  16. // If a user is loaded, then we return it as Ok(user).
  17. Some(m) => Ok(m),
  18. None => {
  19. // When user is still loading, then we create a `Suspension`
  20. // and call `SuspensionHandle::resume` when data loading
  21. // completes, the component will be re-rendered
  22. // automatically.
  23. let (s, handle) = Suspension::new();
  24. on_load_user_complete(move || {handle.resume();});
  25. Err(s)
  26. },
  27. }
  28. }
  29. #[function_component(Content)]
  30. fn content() -> HtmlResult {
  31. let user = use_user()?;
  32. Ok(html! {<div>{"Hello, "}{&user.name}</div>})
  33. }
  34. #[function_component(App)]
  35. fn app() -> Html {
  36. let fallback = html! {<div>{"Loading..."}</div>};
  37. html! {
  38. <Suspense {fallback}>
  39. <Content />
  40. </Suspense>
  41. }
  42. }

Use Suspense in Struct Components

It’s not possible to suspend a struct component directly. However, you can use a function component as a HOC to achieve suspense-based data fetching.

  1. use yew::prelude::*;
  2. #[function_component(WithUser)]
  3. fn with_user<T>() -> HtmlResult
  4. where T: BaseComponent
  5. {
  6. let user = use_user()?;
  7. Ok(html! {<T {user} />})
  8. }
  9. #[derive(Debug, PartialEq, Properties)]
  10. pub struct UserContentProps {
  11. pub user: User,
  12. }
  13. pub struct BaseUserContent;
  14. impl Component for BaseUserContent {
  15. type Properties = UserContentProps;
  16. type Message = ();
  17. fn create(ctx: &Context<Self>) -> Self {
  18. Self
  19. }
  20. fn view(&self, ctx: &Context<Self>) -> Html {
  21. let name = ctx.props().user.name;
  22. html! {<div>{"Hello, "}{name}{"!"}</div>}
  23. }
  24. }
  25. pub type UserContent = WithUser<BaseUserContent>;

Relevant examples