diff --git a/website/docs/tutorial/index.mdx b/website/docs/tutorial/index.mdx
index 5cefaf1b9d1..30887995b09 100644
--- a/website/docs/tutorial/index.mdx
+++ b/website/docs/tutorial/index.mdx
@@ -459,8 +459,6 @@ videos list from an external source. For this we will need to add the following
For making the fetch call.
- [`serde`](https://serde.rs) with derive features
For de-serializing the JSON response
-- [`wasm-bindgen-futures`](https://crates.io/crates/wasm-bindgen-futures)
- For executing Rust Future as a Promise
Let's update the dependencies in `Cargo.toml` file:
@@ -470,7 +468,6 @@ Let's update the dependencies in `Cargo.toml` file:
+yew = { git = "https://github.com/yewstack/yew/", features = ["csr", "serde"] }
+gloo-net = "0.6"
+serde = { version = "1.0", features = ["derive"] }
-+wasm-bindgen-futures = "0.4"
```
Yew's `serde` feature enables integration with the `serde` crate, the important point for us is that
@@ -481,12 +478,16 @@ When choosing dependencies make sure they are `wasm32` compatible!
Otherwise you won't be able to run your application.
:::
-Update the `Video` struct to derive the `Deserialize` trait:
+Update the imports:
-```rust {2,4-5}
+```rust {2}
use yew::prelude::*;
+use serde::Deserialize;
-// ...
+```
+
+Update the `Video` struct to derive the `Deserialize` trait:
+
+```rust {2}
-#[derive(Clone, PartialEq)]
+#[derive(Clone, PartialEq, Deserialize)]
struct Video {
@@ -497,12 +498,71 @@ struct Video {
}
```
-Now as the last step, we need to update our `App` component to make the fetch request instead of using hardcoded data
+Now we need to update our `App` component to fetch data. The modern yew way to do this is with [`use_future`](https://docs.rs/yew/0.23.0/yew/suspense/fn.use_future.html) and [``](https://yew.rs/docs/concepts/suspense).
+
+Alternatively, you can use [`yew::platform::spawn_local`](https://docs.rs/yew/latest/yew/platform/fn.spawn_local.html)
+if hooks are unavailable, such as within struct components or standard functions.
+
+`use_future` suspends the component until the async operation completes, and `` shows a
+fallback UI (e.g. a loading indicator) in the meantime.
-```rust {2,6-50,59-60}
+Update the imports:
+
+```rust {3-4}
use yew::prelude::*;
+use serde::Deserialize;
+use gloo_net::http::Request;
++use yew::suspense::use_future;
+```
+We split the data fetching logic into a child component (`VideosFetcher`) that returns `HtmlResult`,
+while the parent `App` wraps it in ``:
+
+```rust {2-6,7-39}
+use yew::suspense::use_future;
++#[derive(Properties, PartialEq)]
++struct VideosFetchProps {
++ on_click: Callback
++ if let Some(video) = selected_video {
++
++ }
++ >
++ }),
++ Err(err) => Ok(html! {
++ {format!("Error fetching videos: {err}")}
++ }),
++ }
++}
+```
+
+Now we will use the new component inside the App component.
+
+```rust {3-30,37-46}
#[component]
fn App() -> Html {
- let videos = vec![
@@ -532,42 +592,32 @@ fn App() -> Html {
- },
- ];
-
-+ let videos = use_state(|| vec![]);
-+ {
-+ let videos = videos.clone();
-+ use_effect_with((), move |_| {
-+ let videos = videos.clone();
-+ wasm_bindgen_futures::spawn_local(async move {
-+ let fetched_videos: Vec = Request::get("https://yew.rs/tutorial/data.json")
-+ .send()
-+ .await
-+ .unwrap()
-+ .json()
-+ .await
-+ .unwrap();
-+ videos.set(fetched_videos);
-+ });
-+ || ()
-+ });
-+ }
// ...
html! {
- { "RustConf Explorer" }
-
-
{ "Videos to watch" }
+-
{ "RustConf Explorer" }
+-
+-
{ "Videos to watch" }
-
-+
-
- // ...
+-
++ <>
++ { "RustConf Explorer" }
++ {"Loading..."} }} >
++
++
++ >
}
}
```
:::note
-We are using `unwrap`s here because this is a demo application. In a real-world app, you would likely want to have
-[proper error handling](https://doc.rust-lang.org/book/ch09-02-recoverable-errors-with-result.html).
+We use `?` on the `use_future` call to propagate the suspension. The component returns `HtmlResult`
+instead of `Html` when using suspense hooks. The parent wraps it in `` to show
+a loading indicator while the data is being fetched.
:::
Now, look at the browser to see everything working as expected... which would have been the case if it were not for CORS.
@@ -575,9 +625,9 @@ To fix that, we need a proxy server. Luckily trunk provides that.
Update the following line:
-```rust {2-3}
-- let fetched_videos: Vec = Request::get("https://yew.rs/tutorial/data.json")
-+ let fetched_videos: Vec = Request::get("/tutorial/data.json")
+```rust {1-2}
+- Request::get("https://yew.rs/tutorial/data.json")
++ Request::get("/tutorial/data.json")
```
Now, rerun the server with the following command:
@@ -588,6 +638,26 @@ trunk serve --proxy-backend=https://yew.rs/tutorial
Refresh the tab and everything should work as expected.
+The loading text may not be visible because the data loads very quickly. If you'd like to see it, you can add an
+artificial delay inside the `use_future` hook using `TimeoutFuture`. First, add `gloo-timers` to your `Cargo.toml`:
+
+```toml
+gloo-timers = { version = "0.3", features = ["futures"] }
+```
+
+Then add the delay inside the hook:
+
+```
+let videos = use_future(|| async {
++ gloo_timers::future::TimeoutFuture::new(3_000).await; // 3 second delay
+ Request::get("/tutorial/data.json")
+ .send()
+ .await?
+ .json::>()
+ .await
+})?;
+```
+
## Wrapping up
Congratulations! You’ve created a web application that fetches data from an external API and displays a list of videos.
diff --git a/website/versioned_docs/version-0.22/tutorial/index.mdx b/website/versioned_docs/version-0.22/tutorial/index.mdx
index 98c606e7897..3b9b1d3dbd2 100644
--- a/website/versioned_docs/version-0.22/tutorial/index.mdx
+++ b/website/versioned_docs/version-0.22/tutorial/index.mdx
@@ -468,8 +468,6 @@ videos list from an external source. For this we will need to add the following
For making the fetch call.
- [`serde`](https://serde.rs) with derive features
For de-serializing the JSON response
-- [`wasm-bindgen-futures`](https://crates.io/crates/wasm-bindgen-futures)
- For executing Rust Future as a Promise
Let's update the dependencies in `Cargo.toml` file:
@@ -479,7 +477,6 @@ Let's update the dependencies in `Cargo.toml` file:
+yew = { version = "0.22", features = ["csr", "serde"] }
+gloo-net = "0.6"
+serde = { version = "1.0", features = ["derive"] }
-+wasm-bindgen-futures = "0.4"
```
Yew's `serde` feature enables integration with the `serde` crate, the important point for us is that
@@ -490,12 +487,16 @@ When choosing dependencies make sure they are `wasm32` compatible!
Otherwise you won't be able to run your application.
:::
-Update the `Video` struct to derive the `Deserialize` trait:
+Update the imports:
-```rust {2,4-5}
+```rust {2}
use yew::prelude::*;
+use serde::Deserialize;
-// ...
+```
+
+Update the `Video` struct to derive the `Deserialize` trait:
+
+```rust {2}
-#[derive(Clone, PartialEq)]
+#[derive(Clone, PartialEq, Deserialize)]
struct Video {
@@ -506,12 +507,71 @@ struct Video {
}
```
-Now as the last step, we need to update our `App` component to make the fetch request instead of using hardcoded data
+Now we need to update our `App` component to fetch data. The modern yew way to do this is with [`use_future`](https://docs.rs/yew/0.22.0/yew/suspense/fn.use_future.html) and [``](https://yew.rs/docs/concepts/suspense).
+
+Alternatively, you can use [`yew::platform::spawn_local`](https://docs.rs/yew/0.22.0/yew/platform/fn.spawn_local.html)
+if hooks are unavailable, such as within struct components or standard functions.
+
+`use_future` suspends the component until the async operation completes, and `` shows a
+fallback UI (e.g. a loading indicator) in the meantime.
+
+Update the imports:
-```rust {2,6-50,59-60}
+```rust {3-4}
use yew::prelude::*;
+use serde::Deserialize;
+use gloo_net::http::Request;
++use yew::suspense::use_future;
+```
+
+We split the data fetching logic into a child component (`VideosFetcher`) that returns `HtmlResult`,
+while the parent `App` wraps it in ``:
+
+```rust {2-6,7-39}
+use yew::suspense::use_future;
++#[derive(Properties, PartialEq)]
++struct VideosFetchProps {
++ on_click: Callback,
++ selected_video: Option,
++}
+
++#[component]
++fn VideosFetcher(
++ VideosFetchProps {
++ on_click,
++ selected_video,
++ }: &VideosFetchProps,
++) -> HtmlResult {
++ let videos = use_future(|| async {
++ Request::get("https://yew.rs/tutorial/data.json")
++ .send()
++ .await?
++ .json::>()
++ .await
++ })?;
++
++ match &*videos {
++ Ok(videos) => Ok(html! {
++ <>
++
++
{ "Videos to watch" }
++
++
++ if let Some(video) = selected_video {
++
++ }
++ >
++ }),
++ Err(err) => Ok(html! {
++ {format!("Error fetching videos: {err}")}
++ }),
++ }
++}
+```
+Now we will use the new component inside the App component.
+
+```rust {3-30,37-46}
#[component]
fn App() -> Html {
- let videos = vec![
@@ -541,44 +601,32 @@ fn App() -> Html {
- },
- ];
-
-+ let videos = use_state(|| vec![]);
-+ {
-+ let videos = videos.clone();
-+ use_effect_with((), move |_| {
-+ let videos = videos.clone();
-+ wasm_bindgen_futures::spawn_local(async move {
-+ let fetched_videos: Vec = Request::get("https://yew.rs/tutorial/data.json")
-+ .send()
-+ .await
-+ .unwrap()
-+ .json()
-+ .await
-+ .unwrap();
-+ videos.set(fetched_videos);
-+ });
-+ || ()
-+ });
-+ }
// ...
html! {
- <>
- { "RustConf Explorer" }
-
-
{ "Videos to watch" }
--
-+
-
- // ...
- >
+- { "RustConf Explorer" }
+-
+-
{ "Videos to watch" }
+-
+-
++ <>
++ { "RustConf Explorer" }
++ {"Loading..."} }} >
++
++
++ >
}
}
```
:::note
-We are using `unwrap`s here because this is a demo application. In a real-world app, you would likely want to have
-[proper error handling](https://doc.rust-lang.org/book/ch09-02-recoverable-errors-with-result.html).
+We use `?` on the `use_future` call to propagate the suspension. The component returns `HtmlResult`
+instead of `Html` when using suspense hooks. The parent wraps it in `` to show
+a loading indicator while the data is being fetched.
:::
Now, look at the browser to see everything working as expected... which would have been the case if it were not for CORS.
@@ -586,9 +634,9 @@ To fix that, we need a proxy server. Luckily trunk provides that.
Update the following line:
-```rust {2-3}
-- let fetched_videos: Vec = Request::get("https://yew.rs/tutorial/data.json")
-+ let fetched_videos: Vec = Request::get("/tutorial/data.json")
+```rust {1-2}
+- Request::get("https://yew.rs/tutorial/data.json")
++ Request::get("/tutorial/data.json")
```
Now, rerun the server with the following command:
@@ -599,6 +647,26 @@ trunk serve --proxy-backend=https://yew.rs/tutorial
Refresh the tab and everything should work as expected.
+The loading text may not be visible because the data loads very quickly. If you'd like to see it, you can add an
+artificial delay inside the `use_future` hook using `TimeoutFuture`. First, add `gloo-timers` to your `Cargo.toml`:
+
+```toml
+gloo-timers = { version = "0.3", features = ["futures"] }
+```
+
+Then add the delay inside the hook:
+
+```
+let videos = use_future(|| async {
++ gloo_timers::future::TimeoutFuture::new(3_000).await; // 3 second delay
+ Request::get("/tutorial/data.json")
+ .send()
+ .await?
+ .json::>()
+ .await
+
+```
+
## Wrapping up
Congratulations! You’ve created a web application that fetches data from an external API and displays a list of videos.
diff --git a/website/versioned_docs/version-0.23/tutorial/index.mdx b/website/versioned_docs/version-0.23/tutorial/index.mdx
index 4b13c2d931d..ad2422158af 100644
--- a/website/versioned_docs/version-0.23/tutorial/index.mdx
+++ b/website/versioned_docs/version-0.23/tutorial/index.mdx
@@ -468,8 +468,6 @@ videos list from an external source. For this we will need to add the following
For making the fetch call.
- [`serde`](https://serde.rs) with derive features
For de-serializing the JSON response
-- [`wasm-bindgen-futures`](https://crates.io/crates/wasm-bindgen-futures)
- For executing Rust Future as a Promise
Let's update the dependencies in `Cargo.toml` file:
@@ -479,7 +477,6 @@ Let's update the dependencies in `Cargo.toml` file:
+yew = { version = "0.23", features = ["csr", "serde"] }
+gloo-net = "0.6"
+serde = { version = "1.0", features = ["derive"] }
-+wasm-bindgen-futures = "0.4"
```
Yew's `serde` feature enables integration with the `serde` crate, the important point for us is that
@@ -490,12 +487,16 @@ When choosing dependencies make sure they are `wasm32` compatible!
Otherwise you won't be able to run your application.
:::
-Update the `Video` struct to derive the `Deserialize` trait:
+Update the imports:
-```rust {2,4-5}
+```rust {2}
use yew::prelude::*;
+use serde::Deserialize;
-// ...
+```
+
+Update the `Video` struct to derive the `Deserialize` trait:
+
+```rust {2}
-#[derive(Clone, PartialEq)]
+#[derive(Clone, PartialEq, Deserialize)]
struct Video {
@@ -506,12 +507,71 @@ struct Video {
}
```
-Now as the last step, we need to update our `App` component to make the fetch request instead of using hardcoded data
+Now we need to update our `App` component to fetch data. The modern yew way to do this is with [`use_future`](https://docs.rs/yew/0.23.0/yew/suspense/fn.use_future.html) and [``](https://yew.rs/docs/concepts/suspense).
+
+Alternatively, you can use [`yew::platform::spawn_local`](https://docs.rs/yew/latest/yew/platform/fn.spawn_local.html)
+if hooks are unavailable, such as within struct components or standard functions.
+
+`use_future` suspends the component until the async operation completes, and `` shows a
+fallback UI (e.g. a loading indicator) in the meantime.
+
+Update the imports:
-```rust {2,6-50,59-60}
+```rust {3-4}
use yew::prelude::*;
+use serde::Deserialize;
+use gloo_net::http::Request;
++use yew::suspense::use_future;
+```
+
+We split the data fetching logic into a child component (`VideosFetcher`) that returns `HtmlResult`,
+while the parent `App` wraps it in ``:
+
+```rust {2-6,7-39}
+use yew::suspense::use_future;
++#[derive(Properties, PartialEq)]
++struct VideosFetchProps {
++ on_click: Callback,
++ selected_video: Option,
++}
+
++#[component]
++fn VideosFetcher(
++ VideosFetchProps {
++ on_click,
++ selected_video,
++ }: &VideosFetchProps,
++) -> HtmlResult {
++ let videos = use_future(|| async {
++ Request::get("https://yew.rs/tutorial/data.json")
++ .send()
++ .await?
++ .json::>()
++ .await
++ })?;
++
++ match &*videos {
++ Ok(videos) => Ok(html! {
++ <>
++
++
{ "Videos to watch" }
++
++
++ if let Some(video) = selected_video {
++
++ }
++ >
++ }),
++ Err(err) => Ok(html! {
++ {format!("Error fetching videos: {err}")}
++ }),
++ }
++}
+```
+Now we will use the new component inside the App component.
+
+```rust {3-30,37-46}
#[component]
fn App() -> Html {
- let videos = vec![
@@ -541,44 +601,32 @@ fn App() -> Html {
- },
- ];
-
-+ let videos = use_state(|| vec![]);
-+ {
-+ let videos = videos.clone();
-+ use_effect_with((), move |_| {
-+ let videos = videos.clone();
-+ wasm_bindgen_futures::spawn_local(async move {
-+ let fetched_videos: Vec = Request::get("https://yew.rs/tutorial/data.json")
-+ .send()
-+ .await
-+ .unwrap()
-+ .json()
-+ .await
-+ .unwrap();
-+ videos.set(fetched_videos);
-+ });
-+ || ()
-+ });
-+ }
// ...
html! {
- <>
- { "RustConf Explorer" }
-
-
{ "Videos to watch" }
--
-+
-
- // ...
- >
+- { "RustConf Explorer" }
+-
+-
{ "Videos to watch" }
+-
+-
++ <>
++ { "RustConf Explorer" }
++ {"Loading..."} }} >
++
++
++ >
}
}
```
:::note
-We are using `unwrap`s here because this is a demo application. In a real-world app, you would likely want to have
-[proper error handling](https://doc.rust-lang.org/book/ch09-02-recoverable-errors-with-result.html).
+We use `?` on the `use_future` call to propagate the suspension. The component returns `HtmlResult`
+instead of `Html` when using suspense hooks. The parent wraps it in `` to show
+a loading indicator while the data is being fetched.
:::
Now, look at the browser to see everything working as expected... which would have been the case if it were not for CORS.
@@ -586,9 +634,9 @@ To fix that, we need a proxy server. Luckily trunk provides that.
Update the following line:
-```rust {2-3}
-- let fetched_videos: Vec = Request::get("https://yew.rs/tutorial/data.json")
-+ let fetched_videos: Vec = Request::get("/tutorial/data.json")
+```rust {1-2}
+- Request::get("https://yew.rs/tutorial/data.json")
++ Request::get("/tutorial/data.json")
```
Now, rerun the server with the following command:
@@ -599,6 +647,26 @@ trunk serve --proxy-backend=https://yew.rs/tutorial
Refresh the tab and everything should work as expected.
+The loading text may not be visible because the data loads very quickly. If you'd like to see it, you can add an
+artificial delay inside the `use_future` hook using `TimeoutFuture`. First, add `gloo-timers` to your `Cargo.toml`:
+
+```toml
+gloo-timers = { version = "0.3", features = ["futures"] }
+```
+
+Then add the delay inside the hook:
+
+```
+let videos = use_future(|| async {
++ gloo_timers::future::TimeoutFuture::new(3_000).await; // 3 second delay
+ Request::get("/tutorial/data.json")
+ .send()
+ .await?
+ .json::>()
+ .await
+
+```
+
## Wrapping up
Congratulations! You’ve created a web application that fetches data from an external API and displays a list of videos.