hi I am new to Yew and relatively new to Rust as well. I am creating a small component that polls an Api and displays the running status. It is supposed to call the endpoint every 5 seconds and update the component with the new status. Please check the following code.
The code is working, but the problem is instead of making one call to api every 5 seconds, it doubles the call every 5 seconds.
0 sec - call api,
5 sec - call api, call api,
10 sec - call api, call api, call api, call api
15 sec - call api x8,
20 sec - call api x16,
...
It seems the reason is that I used an async block in the for_each closure, but gloo_net::Request send() requires running in an async block.
One thing I observed was that in an error scenario, if the deserialization failed on converting to the RunningStatus, then the calling thread errored out and it would not double every 5 seconds. This looks like another hint to solve it, but I could not find a way to stop the thread after finishing the Ok() => {} block.
use std::{fmt::Display};
use futures_util::{stream::StreamExt};
use gloo_net::http::{Request};
use gloo_timers::{
future::IntervalStream,
};
use serde::Deserialize;
use wasm_bindgen_futures::spawn_local;
use web_sys::console;
use yew::{function_component, html, use_state, Html};
#[derive(Default, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RunningStatus {
pub is_running: bool,
pub batch_number: i32,
pub date_range: DateRange,
}
#[derive(Default, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct DateRange {
pub from: String,
pub to: String,
}
impl Display for RunningStatus {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"is running {}, batch number: {}, date range {}",
self.is_running, self.batch_number, self.date_range
)
}
}
impl Display for DateRange {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "from {} to {}", self.from, self.to)
}
}
#[function_component]
pub fn ApiPoller() -> Html {
let status = use_state(|| RunningStatus::default());
let status_clone = status.clone();
spawn_local(async move {
IntervalStream::new(5_000)
.for_each(|_| async {
let status = status_clone.clone();
let resp = Request::get("/api/v1/process").send().await;
match resp {
Ok(r) => {
let rs = r.json::<RunningStatus>().await;
match rs {
Ok(s) => {
status.set(s);
}
Err(e) => {
console::log_1(&e.to_string().into());
()
}
};
}
Err(_) => {
()
}
};
})
.await;
()
});
html! {
<div>
<p>{ (*status).clone() }</p>
</div>
}
}