Add a timer example that uses function components. (#3128)

* Start a functional timer example.

* Start on the actions.

* wip

* Clean up a bit.

* More sensible times.

* Wip

* Add canceled message.

* Add a distinction between Cancel and Clear.

* Bring timer_function up to parity with the class based example.

* Fix clippy warnings.

* cargo fmt

* Update README.

* Update examples/timer_functional/src/main.rs

Co-authored-by: Jedd Dryden <40693089+Jaffa-Cakes@users.noreply.github.com>

* Add README.md

* Fix wqREADME.md spacing.

* Add key to message iterator.

* Update to new use_effect_with signature.

---------

Co-authored-by: Jedd Dryden <40693089+Jaffa-Cakes@users.noreply.github.com>
Co-authored-by: = <=>
This commit is contained in:
Jay Graves 2023-04-05 20:39:27 +02:00 committed by GitHub
parent f59c744efb
commit 8549ebe361
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 241 additions and 2 deletions

View File

@ -59,6 +59,7 @@ As an example, check out the TodoMVC example here: <https://examples.yew.rs/todo
| [ssr_router](ssr_router) | [F] | Demonstrates server-side rendering with routing. |
| [suspense](suspense) | [F] | This is an example that demonstrates `<Suspense />` support. |
| [timer](timer) | [S] | Demonstrates the use of the interval and timeout services. |
| [timer_functional](timer_functional) | [F] | Demonstrates the use of the interval and timeout services using function components |
| [todomvc](todomvc) | [S] | Implementation of [TodoMVC](http://todomvc.com/). |
| [two_apps](two_apps) | [S] | Runs two separate Yew apps which can communicate with each other. |
| [web_worker_fib](web_worker_fib) | [S] | Calculate Fibonacci numbers in a web worker thread using [`gloo-worker`](https://docs.rs/gloo-worker/latest/gloo_worker/). |

View File

@ -60,7 +60,7 @@ impl Component for App {
Msg::StartTimeout => {
let handle = {
let link = ctx.link().clone();
Timeout::new(3, move || link.send_message(Msg::Done))
Timeout::new(3000, move || link.send_message(Msg::Done))
};
self.timeout = Some(handle);
@ -75,7 +75,7 @@ impl Component for App {
Msg::StartInterval => {
let handle = {
let link = ctx.link().clone();
Interval::new(1, move || link.send_message(Msg::Tick))
Interval::new(1000, move || link.send_message(Msg::Tick))
};
self.interval = Some(handle);

View File

@ -0,0 +1,11 @@
[package]
name = "timer_functional"
version = "0.1.0"
authors = ["Zahash <zahash.z@gmail.com>"]
edition = "2021"
license = "MIT OR Apache-2.0"
[dependencies]
gloo = "0.8.0"
js-sys = "0.3.61"
yew = { path = "../../packages/yew", features = ["csr"] }

View File

@ -0,0 +1,17 @@
# Timer Example
[![Demo](https://img.shields.io/website?label=demo&url=https%3A%2F%2Fexamples.yew.rs%2Ftimer_functional)](https://examples.yew.rs/timer_functional)
This is a technical demonstration for how to use timeouts and intervals.
## Concepts
The example mainly demonstrates the use of [`gloo_timer`](https://docs.rs/gloo-timers/ ) but does so using only [`function components`](https://yew.rs/docs/concepts/function-components).
## Running
Run a debug version of this application:
```bash
trunk serve --open
```

View File

@ -0,0 +1,14 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Yew • Timer</title>
<link data-trunk rel="scss" href="index.scss"/>
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@900&display=swap" rel="stylesheet">
<link data-trunk rel="rust" />
</head>
<body></body>
</html>

View File

@ -0,0 +1,29 @@
$font-stack: Roboto, sans-serif;
$primary-color: #008f53;
body {
font: 100% $font-stack;
color: white;
background-color: $primary-color;
}
#buttons {
text-align: right;
}
#wrapper {
overflow: hidden;
width: 100%;
}
#time {
text-align: center;
font-size: 17vh;
padding: 15% 0;
float: left;
}
#messages {
float: right;
}

View File

@ -0,0 +1,167 @@
use std::rc::Rc;
use gloo::timers::callback::{Interval, Timeout};
use yew::prelude::*;
fn get_current_time() -> String {
let date = js_sys::Date::new_0();
String::from(date.to_locale_time_string("en-US"))
}
enum TimerAction {
Add(&'static str),
Cancel,
SetInterval(Interval),
SetTimeout(Timeout),
TimeoutDone,
}
#[derive(Clone, Debug)]
struct TimerState {
messages: Vec<&'static str>,
interval_handle: Option<Rc<Interval>>,
timeout_handle: Option<Rc<Timeout>>,
}
impl PartialEq for TimerState {
fn eq(&self, other: &Self) -> bool {
self.messages == other.messages
&& self.interval_handle.is_some() == other.interval_handle.is_some()
}
}
impl Reducible for TimerState {
type Action = TimerAction;
fn reduce(self: Rc<Self>, action: TimerAction) -> Rc<Self> {
match action {
TimerAction::Add(message) => {
let mut messages = self.messages.clone();
messages.push(message);
Rc::new(TimerState {
messages,
interval_handle: self.interval_handle.clone(),
timeout_handle: self.timeout_handle.clone(),
})
}
TimerAction::SetInterval(t) => Rc::new(TimerState {
messages: vec!["Interval started!"],
interval_handle: Some(Rc::from(t)),
timeout_handle: self.timeout_handle.clone(),
}),
TimerAction::SetTimeout(t) => Rc::new(TimerState {
messages: vec!["Timer started!!"],
interval_handle: self.interval_handle.clone(),
timeout_handle: Some(Rc::from(t)),
}),
TimerAction::TimeoutDone => {
let mut messages = self.messages.clone();
messages.push("Done!");
Rc::new(TimerState {
messages,
interval_handle: self.interval_handle.clone(),
timeout_handle: None,
})
}
TimerAction::Cancel => {
let mut messages = self.messages.clone();
messages.push("Canceled!");
Rc::new(TimerState {
messages,
interval_handle: None,
timeout_handle: None,
})
}
}
}
}
#[function_component(Clock)]
fn clock() -> Html {
let time = use_state(get_current_time);
{
let time = time.clone();
use_effect_with((), |_| {
Interval::new(1000, move || time.set(get_current_time())).forget();
});
}
html!(
<div id="time">{ time.as_str() }</div>
)
}
#[function_component]
fn App() -> Html {
let state = use_reducer(|| TimerState {
messages: Vec::new(),
interval_handle: None,
timeout_handle: None,
});
let mut key = 0;
let messages: Html = state
.messages
.iter()
.map(|message| {
key += 1;
html! { <p key={ key }>{ message }</p> }
})
.collect();
let has_job = state.interval_handle.is_some() || state.timeout_handle.is_some();
let on_add_timeout = {
let state = state.clone();
Callback::from(move |_: MouseEvent| {
let timeout_state = state.clone();
let message_state = state.clone();
let t = Timeout::new(3000, move || {
message_state.dispatch(TimerAction::TimeoutDone);
});
timeout_state.dispatch(TimerAction::SetTimeout(t));
})
};
let on_add_interval = {
let state = state.clone();
Callback::from(move |_: MouseEvent| {
let interval_state = state.clone();
let message_state = state.clone();
let i = Interval::new(1000, move || {
message_state.dispatch(TimerAction::Add("Tick.."));
});
interval_state.dispatch(TimerAction::SetInterval(i));
})
};
let on_cancel = {
Callback::from(move |_: MouseEvent| {
state.dispatch(TimerAction::Cancel);
})
};
html!(
<>
<div id="buttons">
<button disabled={has_job} onclick={on_add_timeout}>{ "Start Timeout" }</button>
<button disabled={has_job} onclick={on_add_interval}>{ "Start Interval" }</button>
<button disabled={!has_job} onclick={on_cancel}>{ "Cancel"}</button>
</div>
<div id="wrapper">
<Clock />
<div id="messages">
{ messages }
</div>
</div>
</>
)
}
fn main() {
yew::Renderer::<App>::new().render();
}