-
-
Notifications
You must be signed in to change notification settings - Fork 1.4k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add Function Components Example (#2088)
* feat(examples): add function components todo example * chore(examples): apply feedback for function components example * chore(examples): apply more feedback for function components example * feat(examples): implement custom hook for edit boolean toggle * chore(examples): prep for merge, add more documentation * chore(examples): add more descriptive comment to function component hook, fix cargo.toml
- Loading branch information
1 parent
35e1ba6
commit 05728e1
Showing
14 changed files
with
597 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
[package] | ||
name = "function_todomvc" | ||
version = "0.1.0" | ||
authors = ["Drew Hutton <drew.hutton@pm.me>"] | ||
edition = "2018" | ||
license = "MIT OR Apache-2.0" | ||
|
||
[dependencies] | ||
serde = { version = "1.0", features = ["derive"] } | ||
strum = "0.21" | ||
strum_macros = "0.21" | ||
gloo = "0.3" | ||
yew = { path = "../../packages/yew" } | ||
|
||
[dependencies.web-sys] | ||
version = "0.3" | ||
features = [ | ||
"HtmlInputElement", | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
# TodoMVC Example | ||
|
||
[![Demo](https://img.shields.io/website?label=demo&url=https%3A%2F%2Fexamples.yew.rs%2Ffunction_todomvc)](https://examples.yew.rs/function_todomvc) | ||
|
||
This is an implementation of [TodoMVC](http://todomvc.com/) for Yew using function components and hooks. | ||
|
||
## Concepts | ||
|
||
- Uses [`function_components`](https://yew.rs/next/concepts/function-components) | ||
- Uses [`gloo_storage`](https://gloo-rs.web.app/docs/storage) to persist the state | ||
|
||
## Improvements | ||
|
||
- Use `yew-router` for the hash based routing | ||
- Clean up the code |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
<!DOCTYPE html> | ||
<html lang="en"> | ||
<head> | ||
<meta charset="utf-8" /> | ||
<meta name="viewport" content="width=device-width, initial-scale=1" /> | ||
<title>Yew • Function TodoMVC</title> | ||
<link | ||
rel="stylesheet" | ||
href="https://cdn.jsdelivr.net/npm/todomvc-common@1.0.5/base.css" | ||
/> | ||
<link | ||
rel="stylesheet" | ||
href="https://cdn.jsdelivr.net/npm/todomvc-app-css@2.3.0/index.css" | ||
/> | ||
</head> | ||
<body></body> | ||
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
pub mod entry; | ||
pub mod filter; | ||
pub mod header_input; | ||
pub mod info_footer; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,117 @@ | ||
use crate::hooks::use_bool_toggle::use_bool_toggle; | ||
use crate::state::Entry as Item; | ||
use web_sys::{HtmlInputElement, MouseEvent}; | ||
use yew::events::{Event, FocusEvent, KeyboardEvent}; | ||
use yew::{function_component, html, Callback, Classes, Properties, TargetCast}; | ||
|
||
#[derive(PartialEq, Properties, Clone)] | ||
pub struct EntryProps { | ||
pub entry: Item, | ||
pub ontoggle: Callback<usize>, | ||
pub onremove: Callback<usize>, | ||
pub onedit: Callback<(usize, String)>, | ||
} | ||
|
||
#[function_component(Entry)] | ||
pub fn entry(props: &EntryProps) -> Html { | ||
let id = props.entry.id; | ||
let mut class = Classes::from("todo"); | ||
|
||
// We use the `use_bool_toggle` hook and set the default value to `false` | ||
// as the default we are not editing the the entry. When we want to edit the | ||
// entry we can call the toggle method on the `UseBoolToggleHandle` | ||
// which will trigger a re-render with the toggle value being `true` for that | ||
// render and after that render the value of toggle will be flipped back to | ||
// its default (`false`). | ||
// We are relying on the behavior of `onblur` and `onkeypress` to cause | ||
// another render so that this component will render again with the | ||
// default value of toggle. | ||
let edit_toggle = use_bool_toggle(false); | ||
let is_editing = *edit_toggle; | ||
|
||
if is_editing { | ||
class.push("editing"); | ||
} | ||
|
||
if props.entry.completed { | ||
class.push("completed"); | ||
} | ||
|
||
html! { | ||
<li {class}> | ||
<div class="view"> | ||
<input | ||
type="checkbox" | ||
class="toggle" | ||
checked={props.entry.completed} | ||
onclick={props.ontoggle.reform(move |_| id)} | ||
/> | ||
<label ondblclick={Callback::once(move |_| { | ||
edit_toggle.toggle(); | ||
})}> | ||
{ &props.entry.description } | ||
</label> | ||
<button class="destroy" onclick={props.onremove.reform(move |_| id)} /> | ||
</div> | ||
<EntryEdit entry={props.entry.clone()} onedit={props.onedit.clone()} editing={is_editing} /> | ||
</li> | ||
} | ||
} | ||
|
||
#[derive(PartialEq, Properties, Clone)] | ||
pub struct EntryEditProps { | ||
pub entry: Item, | ||
pub onedit: Callback<(usize, String)>, | ||
pub editing: bool, | ||
} | ||
|
||
#[function_component(EntryEdit)] | ||
pub fn entry_edit(props: &EntryEditProps) -> Html { | ||
let id = props.entry.id; | ||
|
||
let target_input_value = |e: &Event| { | ||
let input: HtmlInputElement = e.target_unchecked_into(); | ||
input.value() | ||
}; | ||
|
||
let onblur = { | ||
let edit = props.onedit.clone(); | ||
|
||
move |e: FocusEvent| { | ||
let value = target_input_value(&e); | ||
edit.emit((id, value)) | ||
} | ||
}; | ||
|
||
let onkeypress = { | ||
let edit = props.onedit.clone(); | ||
|
||
move |e: KeyboardEvent| { | ||
if e.key() == "Enter" { | ||
let value = target_input_value(&e); | ||
edit.emit((id, value)) | ||
} | ||
} | ||
}; | ||
|
||
let onmouseover = |e: MouseEvent| { | ||
e.target_unchecked_into::<HtmlInputElement>() | ||
.focus() | ||
.unwrap_or_default(); | ||
}; | ||
|
||
if props.editing { | ||
html! { | ||
<input | ||
class="edit" | ||
type="text" | ||
value={props.entry.description.clone()} | ||
{onmouseover} | ||
{onblur} | ||
{onkeypress} | ||
/> | ||
} | ||
} else { | ||
html! { <input type="hidden" /> } | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
use crate::state::Filter as FilterEnum; | ||
use yew::{function_component, html, Callback, Properties}; | ||
|
||
#[derive(PartialEq, Properties)] | ||
pub struct FilterProps { | ||
pub filter: FilterEnum, | ||
pub selected: bool, | ||
pub onset_filter: Callback<FilterEnum>, | ||
} | ||
|
||
#[function_component(Filter)] | ||
pub fn filter(props: &FilterProps) -> Html { | ||
let filter = props.filter; | ||
|
||
let cls = if props.selected { | ||
"selected" | ||
} else { | ||
"not-selected" | ||
}; | ||
|
||
html! { | ||
<li> | ||
<a class={cls} | ||
href={props.filter.as_href()} | ||
onclick={props.onset_filter.reform(move |_| filter)} | ||
> | ||
{ props.filter } | ||
</a> | ||
</li> | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
use web_sys::HtmlInputElement; | ||
use yew::events::KeyboardEvent; | ||
use yew::{function_component, html, Callback, Properties, TargetCast}; | ||
|
||
#[derive(PartialEq, Properties, Clone)] | ||
pub struct HeaderInputProps { | ||
pub onadd: Callback<String>, | ||
} | ||
|
||
#[function_component(HeaderInput)] | ||
pub fn header_input(props: &HeaderInputProps) -> Html { | ||
let onkeypress = { | ||
let onadd = props.onadd.clone(); | ||
|
||
move |e: KeyboardEvent| { | ||
if e.key() == "Enter" { | ||
let input: HtmlInputElement = e.target_unchecked_into(); | ||
let value = input.value(); | ||
|
||
input.set_value(""); | ||
onadd.emit(value); | ||
} | ||
} | ||
}; | ||
|
||
html! { | ||
<input | ||
class="new-todo" | ||
placeholder="What needs to be done?" | ||
{onkeypress} | ||
/> | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
use yew::{function_component, html}; | ||
|
||
#[function_component(InfoFooter)] | ||
pub fn info_footer() -> Html { | ||
html! { | ||
<footer class="info"> | ||
<p>{ "Double-click to edit a todo" }</p> | ||
<p>{ "Written by " }<a href="https://github.com/Yoroshikun/" target="_blank">{ "Drew Hutton <Yoroshi>" }</a></p> | ||
<p>{ "Part of " }<a href="http://todomvc.com/" target="_blank">{ "TodoMVC" }</a></p> | ||
</footer> | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
pub mod use_bool_toggle; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
use std::ops::Deref; | ||
use std::rc::Rc; | ||
use yew::functional::use_hook; | ||
|
||
pub struct UseBoolToggleHandle { | ||
value: bool, | ||
toggle: Rc<dyn Fn()>, | ||
} | ||
|
||
impl UseBoolToggleHandle { | ||
pub fn toggle(self) { | ||
(self.toggle)() | ||
} | ||
} | ||
|
||
impl Deref for UseBoolToggleHandle { | ||
type Target = bool; | ||
|
||
fn deref(&self) -> &Self::Target { | ||
&self.value | ||
} | ||
} | ||
|
||
/// This hook can be used to cause a re-render with the non-default value, which is | ||
/// then reset to the default value after that render. | ||
/// | ||
/// # Arguments | ||
/// | ||
/// * `default` - The default value. | ||
/// | ||
/// # Example | ||
/// ``` | ||
/// use crate::hooks::use_bool_toggle::use_bool_toggle; | ||
/// ... | ||
/// let value = use_bool_toggle(false); | ||
/// ... | ||
/// <button onclick={Callback::once(move |_| { | ||
/// value.toggle(); | ||
/// // This will toggle the value to true. | ||
/// // Then render. | ||
/// // Post render it will toggle back to false skipping the render. | ||
/// })}> | ||
/// ... | ||
/// ``` | ||
pub fn use_bool_toggle(default: bool) -> UseBoolToggleHandle { | ||
use_hook( | ||
|| default, | ||
move |hook, updater| { | ||
updater.post_render(move |state: &mut bool| { | ||
if *state != default { | ||
*state = default; | ||
} | ||
false | ||
}); | ||
|
||
let toggle = Rc::new(move || { | ||
updater.callback(move |st: &mut bool| { | ||
*st = !*st; | ||
true | ||
}) | ||
}); | ||
|
||
UseBoolToggleHandle { | ||
value: *hook, | ||
toggle, | ||
} | ||
}, | ||
|_| {}, | ||
) | ||
} |
Oops, something went wrong.