├── .github └── workflows │ ├── pr.yml │ └── rust.yml ├── .gitignore ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── crates ├── yew-hooks-test │ ├── Cargo.toml │ └── src │ │ └── lib.rs └── yew-hooks │ ├── Cargo.toml │ ├── src │ ├── hooks │ │ ├── mod.rs │ │ ├── use_async.rs │ │ ├── use_before_unload.rs │ │ ├── use_click_away.rs │ │ ├── use_clipboard.rs │ │ ├── use_counter.rs │ │ ├── use_debounce.rs │ │ ├── use_debounce_effect.rs │ │ ├── use_debounce_state.rs │ │ ├── use_default.rs │ │ ├── use_drag.rs │ │ ├── use_drop.rs │ │ ├── use_effect_once.rs │ │ ├── use_effect_update.rs │ │ ├── use_event.rs │ │ ├── use_favicon.rs │ │ ├── use_geolocation.rs │ │ ├── use_hash.rs │ │ ├── use_hovered.rs │ │ ├── use_infinite_scroll.rs │ │ ├── use_interval.rs │ │ ├── use_is_first_mount.rs │ │ ├── use_is_mounted.rs │ │ ├── use_latest.rs │ │ ├── use_list.rs │ │ ├── use_local_storage.rs │ │ ├── use_location.rs │ │ ├── use_logger.rs │ │ ├── use_map.rs │ │ ├── use_measure.rs │ │ ├── use_media.rs │ │ ├── use_mount.rs │ │ ├── use_previous.rs │ │ ├── use_queue.rs │ │ ├── use_raf.rs │ │ ├── use_raf_state.rs │ │ ├── use_renders_count.rs │ │ ├── use_scroll.rs │ │ ├── use_scrolling.rs │ │ ├── use_search_param.rs │ │ ├── use_session_storage.rs │ │ ├── use_set.rs │ │ ├── use_size.rs │ │ ├── use_state_ptr_eq.rs │ │ ├── use_swipe.rs │ │ ├── use_throttle.rs │ │ ├── use_throttle_effect.rs │ │ ├── use_throttle_state.rs │ │ ├── use_timeout.rs │ │ ├── use_title.rs │ │ ├── use_toggle.rs │ │ ├── use_unmount.rs │ │ ├── use_update.rs │ │ ├── use_visible.rs │ │ ├── use_websocket.rs │ │ ├── use_window_scroll.rs │ │ └── use_window_size.rs │ ├── lib.rs │ └── web_sys_ext.rs │ └── tests │ ├── common │ └── mod.rs │ └── use_counter.rs └── examples └── yew-app ├── .gitignore ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── Trunk.toml ├── index.html ├── public ├── favicon.ico └── logo.svg ├── src ├── app │ ├── about.rs │ ├── home.rs │ ├── hooks │ │ ├── mod.rs │ │ ├── use_async.rs │ │ ├── use_before_unload.rs │ │ ├── use_bool_toggle.rs │ │ ├── use_click_away.rs │ │ ├── use_clipboard.rs │ │ ├── use_counter.rs │ │ ├── use_debounce.rs │ │ ├── use_debounce_effect.rs │ │ ├── use_debounce_state.rs │ │ ├── use_default.rs │ │ ├── use_drag.rs │ │ ├── use_drop.rs │ │ ├── use_effect_once.rs │ │ ├── use_effect_update.rs │ │ ├── use_event.rs │ │ ├── use_favicon.rs │ │ ├── use_geolocation.rs │ │ ├── use_hash.rs │ │ ├── use_hovered.rs │ │ ├── use_infinite_scroll.rs │ │ ├── use_interval.rs │ │ ├── use_is_first_mount.rs │ │ ├── use_is_mounted.rs │ │ ├── use_latest.rs │ │ ├── use_list.rs │ │ ├── use_local_storage.rs │ │ ├── use_location.rs │ │ ├── use_logger.rs │ │ ├── use_map.rs │ │ ├── use_measure.rs │ │ ├── use_media.rs │ │ ├── use_mount.rs │ │ ├── use_mut_latest.rs │ │ ├── use_previous.rs │ │ ├── use_queue.rs │ │ ├── use_raf.rs │ │ ├── use_raf_state.rs │ │ ├── use_renders_count.rs │ │ ├── use_scroll.rs │ │ ├── use_scrolling.rs │ │ ├── use_search_param.rs │ │ ├── use_session_storage.rs │ │ ├── use_set.rs │ │ ├── use_size.rs │ │ ├── use_state_ptr_eq.rs │ │ ├── use_swipe.rs │ │ ├── use_throttle.rs │ │ ├── use_throttle_effect.rs │ │ ├── use_throttle_state.rs │ │ ├── use_timeout.rs │ │ ├── use_title.rs │ │ ├── use_toggle.rs │ │ ├── use_unmount.rs │ │ ├── use_update.rs │ │ ├── use_visible.rs │ │ ├── use_websocket.rs │ │ ├── use_window_scroll.rs │ │ └── use_window_size.rs │ └── mod.rs ├── components │ ├── mod.rs │ ├── nav.rs │ └── ui │ │ ├── button.rs │ │ └── mod.rs ├── lib.rs └── main.rs ├── tailwind.config.js ├── tailwind.css └── tests └── lib.rs /.github/workflows/pr.yml: -------------------------------------------------------------------------------- 1 | name: Pull Request 2 | 3 | on: 4 | pull_request: 5 | branches: [ main ] 6 | 7 | env: 8 | CARGO_TERM_COLOR: always 9 | 10 | jobs: 11 | build: 12 | 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - uses: actions/checkout@v2 17 | - name: Setup toolchain 18 | uses: dtolnay/rust-toolchain@master 19 | with: 20 | toolchain: stable 21 | targets: wasm32-unknown-unknown 22 | components: rust-src 23 | - uses: jetli/wasm-pack-action@v0.4.0 24 | with: 25 | # Optional version of wasm-pack to install(eg. '0.9.1', 'latest') 26 | version: 'latest' 27 | - uses: jetli/trunk-action@v0.5.0 28 | with: 29 | # Optional version of trunk to install(eg. 'v0.16.0', 'latest') 30 | version: 'latest' 31 | - name: Cargo fmt & clippy 32 | run: | 33 | cargo fmt --all -- --check 34 | cargo clippy -- --deny=warnings 35 | - name: Tests 36 | run: | 37 | cd crates/yew-hooks 38 | cargo test 39 | - name: Run hooks tests 40 | run: | 41 | cd crates/yew-hooks && wasm-pack test --headless --chrome 42 | - name: Run examples tests 43 | run: | 44 | cd examples/yew-app && wasm-pack test --headless --chrome 45 | - name: Build examples 46 | run: | 47 | cd examples/yew-app && trunk build --public-url=/yew-hooks/ 48 | -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | 7 | env: 8 | CARGO_TERM_COLOR: always 9 | 10 | jobs: 11 | build: 12 | 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - uses: actions/checkout@v2 17 | - name: Setup toolchain 18 | uses: dtolnay/rust-toolchain@master 19 | with: 20 | toolchain: stable 21 | targets: wasm32-unknown-unknown 22 | components: rust-src 23 | - uses: jetli/wasm-pack-action@v0.4.0 24 | with: 25 | # Optional version of wasm-pack to install(eg. '0.9.1', 'latest') 26 | version: 'latest' 27 | - uses: jetli/trunk-action@v0.5.0 28 | with: 29 | # Optional version of trunk to install(eg. 'v0.16.0', 'latest') 30 | version: 'latest' 31 | - name: Cargo fmt & clippy 32 | run: | 33 | cargo fmt --all -- --check 34 | cargo clippy -- --deny=warnings 35 | - name: Tests 36 | run: | 37 | cd crates/yew-hooks 38 | cargo test 39 | - name: Run hooks tests 40 | run: | 41 | cd crates/yew-hooks && wasm-pack test --headless --chrome 42 | - name: Run examples tests 43 | run: | 44 | cd examples/yew-app && wasm-pack test --headless --chrome 45 | - name: Build examples 46 | run: | 47 | cd examples/yew-app && trunk build --public-url=/yew-hooks/ 48 | - name: Deploy 49 | uses: JamesIves/github-pages-deploy-action@v4.2.2 50 | with: 51 | branch: gh-pages 52 | folder: examples/yew-app/dist 53 | clean: true 54 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /target/ 4 | 5 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 6 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 7 | Cargo.lock 8 | 9 | # These are backup files generated by rustfmt 10 | **/*.rs.bk 11 | 12 | /.vscode/ 13 | /.idea/ -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | resolver = "2" 3 | members = [ 4 | "crates/*", 5 | "examples/*", 6 | ] -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Jet Li 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /crates/yew-hooks-test/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "yew-hooks-test" 3 | version = "0.1.0" 4 | edition = "2021" 5 | authors = ["Jet Li "] 6 | categories = ["gui", "wasm", "web-programming"] 7 | keywords = ["wasm", "yew", "hooks"] 8 | description = "Hooks test for the Yew web framework." 9 | license = "Apache-2.0/MIT" 10 | readme = "../../README.md" 11 | repository = "https://github.com/jetli/yew-hooks" 12 | homepage = "https://github.com/jetli/yew-hooks" 13 | documentation = "https://github.com/jetli/yew-hooks" 14 | 15 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 16 | 17 | [dependencies] 18 | yew = { version = "0.21.0", features=["csr"]} 19 | -------------------------------------------------------------------------------- /crates/yew-hooks-test/src/lib.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | mod tests { 3 | #[test] 4 | fn it_works() { 5 | let result = 2 + 2; 6 | assert_eq!(result, 4); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /crates/yew-hooks/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "yew-hooks" 3 | version = "0.3.3" 4 | edition = "2021" 5 | authors = ["Jet Li "] 6 | categories = ["gui", "wasm", "web-programming"] 7 | keywords = ["wasm", "yew", "hooks", "react"] 8 | description = "Hooks for the Yew web framework, inspired by react hook libs like streamich/react-use and alibaba/hooks." 9 | license = "Apache-2.0/MIT" 10 | readme = "../../README.md" 11 | repository = "https://github.com/jetli/yew-hooks" 12 | homepage = "https://github.com/jetli/yew-hooks" 13 | documentation = "https://docs.rs/yew-hooks/" 14 | 15 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 16 | 17 | [dependencies] 18 | log = "0.4" 19 | yew = { version = "0.21.0", features = ["csr"] } 20 | gloo = "0.11" 21 | wasm-bindgen = "0.2" 22 | wasm-bindgen-futures = "0.4" 23 | serde = "1" 24 | js-sys = "0.3" 25 | 26 | [dependencies.web-sys] 27 | version = "0.3" 28 | features = [ 29 | "BeforeUnloadEvent", 30 | "BinaryType", 31 | "Blob", 32 | "CloseEvent", 33 | "Coordinates", 34 | "DataTransfer", 35 | "DataTransferItem", 36 | "DataTransferItemList", 37 | "DomRectReadOnly", 38 | "Element", 39 | "File", 40 | "Geolocation", 41 | "HtmlCollection", 42 | "HtmlLinkElement", 43 | "HtmlMediaElement", 44 | "IntersectionObserver", 45 | "IntersectionObserverEntry", 46 | "MessageEvent", 47 | "Navigator", 48 | "Position", 49 | "PositionError", 50 | "PositionOptions", 51 | "StorageEvent", 52 | "TimeRanges", 53 | "Touch", 54 | "TouchList", 55 | "UrlSearchParams", 56 | "WebSocket", 57 | ] 58 | 59 | [dev-dependencies] 60 | log = "0.4.8" 61 | wasm-bindgen-test = "0.3.14" 62 | -------------------------------------------------------------------------------- /crates/yew-hooks/src/hooks/mod.rs: -------------------------------------------------------------------------------- 1 | mod use_async; 2 | mod use_before_unload; 3 | mod use_click_away; 4 | mod use_clipboard; 5 | mod use_counter; 6 | mod use_debounce; 7 | mod use_debounce_effect; 8 | mod use_debounce_state; 9 | mod use_default; 10 | mod use_drag; 11 | mod use_drop; 12 | mod use_effect_once; 13 | mod use_effect_update; 14 | mod use_event; 15 | mod use_favicon; 16 | mod use_geolocation; 17 | mod use_hash; 18 | mod use_hovered; 19 | mod use_infinite_scroll; 20 | mod use_interval; 21 | mod use_is_first_mount; 22 | mod use_is_mounted; 23 | mod use_latest; 24 | mod use_list; 25 | mod use_local_storage; 26 | mod use_location; 27 | mod use_logger; 28 | mod use_map; 29 | mod use_measure; 30 | mod use_media; 31 | mod use_mount; 32 | mod use_previous; 33 | mod use_queue; 34 | mod use_raf; 35 | mod use_raf_state; 36 | mod use_renders_count; 37 | mod use_scroll; 38 | mod use_scrolling; 39 | mod use_search_param; 40 | mod use_session_storage; 41 | mod use_set; 42 | mod use_size; 43 | mod use_state_ptr_eq; 44 | mod use_swipe; 45 | mod use_throttle; 46 | mod use_throttle_effect; 47 | mod use_throttle_state; 48 | mod use_timeout; 49 | mod use_title; 50 | mod use_toggle; 51 | mod use_unmount; 52 | mod use_update; 53 | mod use_visible; 54 | mod use_websocket; 55 | mod use_window_scroll; 56 | mod use_window_size; 57 | 58 | pub use use_async::*; 59 | pub use use_before_unload::*; 60 | pub use use_click_away::*; 61 | pub use use_clipboard::*; 62 | pub use use_counter::*; 63 | pub use use_debounce::*; 64 | pub use use_debounce_effect::*; 65 | pub use use_debounce_state::*; 66 | pub use use_default::*; 67 | pub use use_drag::*; 68 | pub use use_drop::*; 69 | pub use use_effect_once::*; 70 | pub use use_effect_update::*; 71 | pub use use_event::*; 72 | pub use use_favicon::*; 73 | pub use use_geolocation::*; 74 | pub use use_hash::*; 75 | pub use use_hovered::*; 76 | pub use use_infinite_scroll::*; 77 | pub use use_interval::*; 78 | pub use use_is_first_mount::*; 79 | pub use use_is_mounted::*; 80 | pub use use_latest::*; 81 | pub use use_list::*; 82 | pub use use_local_storage::*; 83 | pub use use_location::*; 84 | pub use use_logger::*; 85 | pub use use_map::*; 86 | pub use use_measure::*; 87 | pub use use_media::*; 88 | pub use use_mount::*; 89 | pub use use_previous::*; 90 | pub use use_queue::*; 91 | pub use use_raf::*; 92 | pub use use_raf_state::*; 93 | pub use use_renders_count::*; 94 | pub use use_scroll::*; 95 | pub use use_scrolling::*; 96 | pub use use_search_param::*; 97 | pub use use_session_storage::*; 98 | pub use use_set::*; 99 | pub use use_size::*; 100 | pub use use_state_ptr_eq::*; 101 | pub use use_swipe::*; 102 | pub use use_throttle::*; 103 | pub use use_throttle_effect::*; 104 | pub use use_throttle_state::*; 105 | pub use use_timeout::*; 106 | pub use use_title::*; 107 | pub use use_toggle::*; 108 | pub use use_unmount::*; 109 | pub use use_update::*; 110 | pub use use_visible::*; 111 | pub use use_websocket::*; 112 | pub use use_window_scroll::*; 113 | pub use use_window_size::*; 114 | -------------------------------------------------------------------------------- /crates/yew-hooks/src/hooks/use_before_unload.rs: -------------------------------------------------------------------------------- 1 | use web_sys::BeforeUnloadEvent; 2 | use yew::prelude::*; 3 | 4 | use super::use_event_with_window; 5 | 6 | /// A side-effect hook that shows browser alert when user try to reload or close the page. 7 | /// 8 | /// # Example 9 | /// 10 | /// ```rust 11 | /// # use yew::prelude::*; 12 | /// # 13 | /// use yew_hooks::prelude::*; 14 | /// 15 | /// #[function_component(UseBeforeUnload)] 16 | /// fn before_unload() -> Html { 17 | /// use_before_unload(true, "You have unsaved changes, are you sure?".to_string()); 18 | /// 19 | /// html! { 20 | /// <> 21 | /// 22 | /// } 23 | /// } 24 | /// ``` 25 | #[hook] 26 | pub fn use_before_unload(enabled: bool, msg: String) { 27 | use_event_with_window("beforeunload", move |e: BeforeUnloadEvent| { 28 | if !enabled { 29 | return; 30 | } 31 | 32 | if !msg.is_empty() { 33 | e.set_return_value(msg.as_str()); 34 | } 35 | 36 | // WebKit-derived browsers don't follow the spec for the dialog box. 37 | // We should return msg in the future for the event handler. 38 | // https://developer.mozilla.org/en-US/docs/Web/API/BeforeUnloadEvent 39 | }); 40 | } 41 | -------------------------------------------------------------------------------- /crates/yew-hooks/src/hooks/use_click_away.rs: -------------------------------------------------------------------------------- 1 | use std::rc::Rc; 2 | 3 | use web_sys::Node; 4 | use yew::{prelude::*, TargetCast}; 5 | 6 | use super::{use_event_with_window, use_latest}; 7 | 8 | /// A hook that triggers a callback when user clicks outside the target element. 9 | /// 10 | /// # Example 11 | /// 12 | /// ```rust 13 | /// # use yew::prelude::*; 14 | /// # use log::debug; 15 | /// # 16 | /// use yew_hooks::prelude::*; 17 | /// 18 | /// #[function_component(UseClickAway)] 19 | /// fn click_away() -> Html { 20 | /// let node = use_node_ref(); 21 | /// 22 | /// use_click_away(node.clone(), move |_: Event| { 23 | /// debug!("Clicked outside!"); 24 | /// }); 25 | /// 26 | /// html! { 27 | ///
28 | /// { "Try to click outside of this area." } 29 | ///
30 | /// } 31 | /// } 32 | /// ``` 33 | #[hook] 34 | pub fn use_click_away(node: NodeRef, callback: F) 35 | where 36 | F: Fn(Event) + 'static, 37 | { 38 | let callback_ref = use_latest(callback); 39 | 40 | let handler = Rc::new(move |e: Event| { 41 | if let Some(node) = &node.get() { 42 | if let Some(target_node) = e.target_dyn_into::() { 43 | if !node.contains(Some(&target_node)) { 44 | (*callback_ref.current())(e); 45 | } 46 | } 47 | } 48 | }); 49 | 50 | { 51 | let handler = handler.clone(); 52 | use_event_with_window("mousedown", move |e: Event| { 53 | handler(e); 54 | }); 55 | } 56 | 57 | use_event_with_window("touchstart", move |e: Event| { 58 | handler(e); 59 | }); 60 | } 61 | -------------------------------------------------------------------------------- /crates/yew-hooks/src/hooks/use_debounce.rs: -------------------------------------------------------------------------------- 1 | use yew::prelude::*; 2 | 3 | use super::{use_timeout, UseTimeoutHandle}; 4 | 5 | /// State handle for the [`use_debounce`] hook. 6 | pub struct UseDebounceHandle { 7 | inner: UseTimeoutHandle, 8 | } 9 | 10 | impl UseDebounceHandle { 11 | /// Run the debounce. 12 | pub fn run(&self) { 13 | self.inner.reset(); 14 | } 15 | 16 | /// Cancel the debounce. 17 | pub fn cancel(&self) { 18 | self.inner.cancel(); 19 | } 20 | } 21 | 22 | impl Clone for UseDebounceHandle { 23 | fn clone(&self) -> Self { 24 | Self { 25 | inner: self.inner.clone(), 26 | } 27 | } 28 | } 29 | 30 | /// A hook that delays invoking a function until after wait milliseconds have elapsed 31 | /// since the last time the debounced function was invoked. 32 | /// 33 | /// # Example 34 | /// 35 | /// ```rust 36 | /// # use web_sys::HtmlInputElement; 37 | /// # use yew::prelude::*; 38 | /// # 39 | /// use yew_hooks::prelude::*; 40 | /// 41 | /// #[function_component(Debounce)] 42 | /// fn debounce() -> Html { 43 | /// let status = use_state(|| "Typing stopped".to_string()); 44 | /// let value = use_state(|| "".to_string()); 45 | /// let debounced_value = use_state(|| "".to_string()); 46 | /// 47 | /// let debounce = { 48 | /// let value = value.clone(); 49 | /// let status = status.clone(); 50 | /// let debounced_value = debounced_value.clone(); 51 | /// use_debounce( 52 | /// move || { 53 | /// debounced_value.set((*value).clone()); 54 | /// status.set("Typing stopped".to_string()); 55 | /// }, 56 | /// 2000, 57 | /// ) 58 | /// }; 59 | /// 60 | /// let oninput = { 61 | /// let status = status.clone(); 62 | /// let value = value.clone(); 63 | /// let debounce = debounce.clone(); 64 | /// Callback::from(move |e: InputEvent| { 65 | /// let input: HtmlInputElement = e.target_unchecked_into(); 66 | /// value.set(input.value()); 67 | /// status.set("Waiting for typing to stop...".to_string()); 68 | /// debounce.run(); 69 | /// }) 70 | /// }; 71 | /// 72 | /// let onclick = { Callback::from(move |_| debounce.cancel()) }; 73 | /// 74 | /// html! { 75 | /// <> 76 | /// 77 | /// 78 | ///

{&*status}

79 | ///

80 | /// { "Value: " } {&*value} 81 | ///

82 | ///

83 | /// { "Debounced value: " } {&*debounced_value} 84 | ///

85 | /// 86 | /// } 87 | /// } 88 | /// ``` 89 | #[hook] 90 | pub fn use_debounce(callback: Callback, millis: u32) -> UseDebounceHandle 91 | where 92 | Callback: FnOnce() + 'static, 93 | { 94 | let inner = use_timeout(callback, millis); 95 | 96 | UseDebounceHandle { inner } 97 | } 98 | -------------------------------------------------------------------------------- /crates/yew-hooks/src/hooks/use_debounce_effect.rs: -------------------------------------------------------------------------------- 1 | use yew::prelude::*; 2 | 3 | use super::{use_debounce, use_unmount}; 4 | 5 | /// A hook that delays calling effect callback until after wait milliseconds have elapsed 6 | /// since the last time effect callback was called. 7 | /// 8 | /// # Example 9 | /// 10 | /// ```rust 11 | /// # use web_sys::HtmlInputElement; 12 | /// # use yew::prelude::*; 13 | /// # 14 | /// use yew_hooks::prelude::*; 15 | /// 16 | /// #[function_component(DebounceEffect)] 17 | /// fn debounce_effect() -> Html { 18 | /// let status = use_state(|| "Typing stopped".to_string()); 19 | /// let value = use_state(|| "".to_string()); 20 | /// let debounced_value = use_state(|| "".to_string()); 21 | /// 22 | /// { 23 | /// let status = status.clone(); 24 | /// let value = value.clone(); 25 | /// let debounced_value = debounced_value.clone(); 26 | /// use_debounce_effect( 27 | /// move || { 28 | /// // This will delay updating state. 29 | /// debounced_value.set((*value).clone()); 30 | /// status.set("Typing stopped".to_string()); 31 | /// }, 32 | /// 2000, 33 | /// ); 34 | /// } 35 | /// 36 | /// let oninput = { 37 | /// let status = status.clone(); 38 | /// let value = value.clone(); 39 | /// Callback::from(move |e: InputEvent| { 40 | /// let input: HtmlInputElement = e.target_unchecked_into(); 41 | /// // This will update state every time. 42 | /// value.set(input.value()); 43 | /// status.set("Waiting for typing to stop...".to_string()); 44 | /// }) 45 | /// }; 46 | /// 47 | /// html! { 48 | /// <> 49 | /// 50 | ///

{&*status}

51 | ///

52 | /// { "Value: " } {&*value} 53 | ///

54 | ///

55 | /// { "Debounced value: " } {&*debounced_value} 56 | ///

57 | /// 58 | /// } 59 | /// } 60 | /// ``` 61 | #[hook] 62 | pub fn use_debounce_effect(callback: Callback, millis: u32) 63 | where 64 | Callback: FnOnce() + 'static, 65 | { 66 | let debounce = use_debounce(callback, millis); 67 | 68 | { 69 | let debounce = debounce.clone(); 70 | use_effect(move || { 71 | debounce.run(); 72 | 73 | || () 74 | }); 75 | } 76 | 77 | use_unmount(move || { 78 | debounce.cancel(); 79 | }); 80 | } 81 | 82 | /// This hook is similar to [`use_debounce_effect`] but it accepts dependencies. 83 | /// 84 | /// Whenever the dependencies are changed, the debounce effect is run again. 85 | /// To detect changes, dependencies must implement `PartialEq`. 86 | #[hook] 87 | pub fn use_debounce_effect_with_deps( 88 | callback: Callback, 89 | millis: u32, 90 | deps: Dependents, 91 | ) where 92 | Callback: FnOnce() + 'static, 93 | Dependents: PartialEq + 'static, 94 | { 95 | let debounce = use_debounce(callback, millis); 96 | 97 | { 98 | let debounce = debounce.clone(); 99 | use_effect_with(deps, move |_| { 100 | debounce.run(); 101 | 102 | || () 103 | }); 104 | } 105 | 106 | use_unmount(move || { 107 | debounce.cancel(); 108 | }); 109 | } 110 | -------------------------------------------------------------------------------- /crates/yew-hooks/src/hooks/use_debounce_state.rs: -------------------------------------------------------------------------------- 1 | use std::ops::Deref; 2 | use std::rc::Rc; 3 | 4 | use yew::prelude::*; 5 | 6 | use super::use_debounce; 7 | 8 | /// State handle for the [`use_debounce_state`] hook. 9 | pub struct UseDebounceStateHandle { 10 | inner: UseStateHandle, 11 | set: Rc, 12 | } 13 | 14 | impl UseDebounceStateHandle { 15 | // Set the value. 16 | pub fn set(&self, value: T) { 17 | (self.set)(value); 18 | } 19 | } 20 | 21 | impl Deref for UseDebounceStateHandle { 22 | type Target = T; 23 | 24 | fn deref(&self) -> &Self::Target { 25 | &self.inner 26 | } 27 | } 28 | 29 | impl Clone for UseDebounceStateHandle { 30 | fn clone(&self) -> Self { 31 | Self { 32 | inner: self.inner.clone(), 33 | set: self.set.clone(), 34 | } 35 | } 36 | } 37 | 38 | impl PartialEq for UseDebounceStateHandle 39 | where 40 | T: PartialEq, 41 | { 42 | fn eq(&self, other: &Self) -> bool { 43 | *self.inner == *other.inner 44 | } 45 | } 46 | 47 | /// A hook that delays updating state until after wait milliseconds have elapsed 48 | /// since the last time state was updated. 49 | /// 50 | /// # Example 51 | /// 52 | /// ```rust 53 | /// # use web_sys::HtmlInputElement; 54 | /// # use yew::prelude::*; 55 | /// # 56 | /// use yew_hooks::prelude::*; 57 | /// 58 | /// #[function_component(DebounceState)] 59 | /// fn debounce_state() -> Html { 60 | /// let state = use_debounce_state(|| "".to_string(), 2000); 61 | /// 62 | /// let oninput = { 63 | /// let state = state.clone(); 64 | /// Callback::from(move |e: InputEvent| { 65 | /// let input: HtmlInputElement = e.target_unchecked_into(); 66 | /// state.set(input.value()); 67 | /// }) 68 | /// }; 69 | /// 70 | /// html! { 71 | /// <> 72 | /// 73 | /// { "Debounced state: " } {&*state} 74 | /// 75 | /// } 76 | /// } 77 | /// ``` 78 | #[hook] 79 | pub fn use_debounce_state(init_fn: F, millis: u32) -> UseDebounceStateHandle 80 | where 81 | T: 'static, 82 | F: FnOnce() -> T, 83 | { 84 | let value = use_mut_ref(|| None); 85 | let inner = use_state(init_fn); 86 | let debounce = { 87 | let value = value.clone(); 88 | let inner = inner.clone(); 89 | use_debounce( 90 | move || { 91 | let value = (*value.borrow_mut()).take(); 92 | if let Some(value) = value { 93 | inner.set(value); 94 | } 95 | }, 96 | millis, 97 | ) 98 | }; 99 | 100 | let set = { 101 | Rc::new(move |new_value: T| { 102 | *value.borrow_mut() = Some(new_value); 103 | debounce.run(); 104 | }) 105 | }; 106 | 107 | UseDebounceStateHandle { inner, set } 108 | } 109 | -------------------------------------------------------------------------------- /crates/yew-hooks/src/hooks/use_default.rs: -------------------------------------------------------------------------------- 1 | use std::ops::Deref; 2 | use std::rc::Rc; 3 | 4 | use yew::prelude::*; 5 | 6 | /// State handle for the [`use_default`] hook. 7 | pub struct UseDefaultHandle { 8 | inner: UseStateHandle>, 9 | default: Rc, 10 | } 11 | 12 | impl UseDefaultHandle 13 | where 14 | T: 'static, 15 | { 16 | /// Replaces the value. 17 | pub fn set(&self, value: Option) { 18 | self.inner.set(value); 19 | } 20 | } 21 | 22 | impl Deref for UseDefaultHandle { 23 | type Target = T; 24 | 25 | fn deref(&self) -> &Self::Target { 26 | let value = &(*self.inner); 27 | value.as_ref().unwrap_or_else(|| self.default.as_ref()) 28 | } 29 | } 30 | 31 | impl Clone for UseDefaultHandle { 32 | fn clone(&self) -> Self { 33 | Self { 34 | inner: self.inner.clone(), 35 | default: self.default.clone(), 36 | } 37 | } 38 | } 39 | 40 | impl PartialEq for UseDefaultHandle 41 | where 42 | T: PartialEq, 43 | { 44 | fn eq(&self, other: &Self) -> bool { 45 | *self.inner == *other.inner 46 | } 47 | } 48 | 49 | /// A state hook that returns the default value when state is None. 50 | /// 51 | /// # Example 52 | /// 53 | /// ```rust 54 | /// # use yew::prelude::*; 55 | /// # 56 | /// use yew_hooks::prelude::*; 57 | /// 58 | /// #[function_component(UseDefault)] 59 | /// fn default() -> Html { 60 | /// let state = use_default(|| None, "Hello(default)".to_string()); 61 | /// 62 | /// let onclick = { 63 | /// let state = state.clone(); 64 | /// Callback::from(move |_| { 65 | /// state.set(Some("World!".to_string())); 66 | /// }) 67 | /// }; 68 | /// 69 | /// let onclear = { 70 | /// let state = state.clone(); 71 | /// Callback::from(move |_| { 72 | /// state.set(None); 73 | /// }) 74 | /// }; 75 | /// 76 | /// html! { 77 | /// <> 78 | /// 79 | /// 80 | /// { "Current value: " } 81 | /// { &*state } 82 | /// 83 | /// } 84 | /// } 85 | /// ``` 86 | #[hook] 87 | pub fn use_default(init_fn: F, default: T) -> UseDefaultHandle 88 | where 89 | T: 'static, 90 | F: FnOnce() -> Option, 91 | { 92 | let inner = use_state(init_fn); 93 | let default = use_memo((), |_| default); 94 | 95 | UseDefaultHandle { inner, default } 96 | } 97 | -------------------------------------------------------------------------------- /crates/yew-hooks/src/hooks/use_drag.rs: -------------------------------------------------------------------------------- 1 | use web_sys::Element; 2 | use yew::prelude::*; 3 | 4 | use super::{use_event, use_mut_latest}; 5 | 6 | /// Options for drag. 7 | #[derive(Default)] 8 | pub struct UseDragOptions { 9 | /// Callback for `dragstart`. 10 | pub ondragstart: Option>, 11 | /// Callback for `dragend`. 12 | pub ondragend: Option>, 13 | } 14 | 15 | /// State handle for the [`use_drag`] hook. 16 | pub struct UseDragHandle { 17 | /// State for whether is dragging. 18 | pub dragging: UseStateHandle, 19 | } 20 | 21 | /// This hook tracks file, link and copy-paste drags. 22 | /// 23 | /// # Example 24 | /// 25 | /// ```rust 26 | /// # use yew::prelude::*; 27 | /// # 28 | /// use yew_hooks::prelude::*; 29 | /// 30 | /// #[function_component(UseDrag)] 31 | /// fn drag() -> Html { 32 | /// let node = use_node_ref(); 33 | /// let state = use_drag(node.clone()); 34 | /// 35 | /// html! { 36 | ///
37 | ///

38 | /// { " Dragging: " } 39 | /// { *state.dragging } 40 | ///

41 | ///

42 | /// { "Try to drag this area" } 43 | ///

44 | ///
45 | /// } 46 | /// } 47 | /// ``` 48 | #[hook] 49 | pub fn use_drag(node: NodeRef) -> UseDragHandle { 50 | use_drag_with_options(node, UseDragOptions::default()) 51 | } 52 | 53 | /// This hook tracks file, link and copy-paste drags. 54 | /// [`use_drag`] hook with options. 55 | /// 56 | /// # Example 57 | /// 58 | /// ```rust 59 | /// # use yew::prelude::*; 60 | /// # 61 | /// use yew_hooks::prelude::*; 62 | /// 63 | /// #[function_component(UseDrag)] 64 | /// fn drag() -> Html { 65 | /// let node = use_node_ref(); 66 | /// let state = use_drag_with_options(node.clone(), UseDragOptions { 67 | /// ondragstart: Some(Box::new(move |e| { 68 | /// if let Some(data_transfer) = e.data_transfer() { 69 | /// let _ = data_transfer.set_data("text", "hello"); 70 | /// } 71 | /// })), 72 | /// ondragend: Some(Box::new(move |e| { 73 | /// })), 74 | /// }); 75 | /// 76 | /// html! { 77 | ///
78 | ///

79 | /// { " Dragging: " } 80 | /// { *state.dragging } 81 | ///

82 | ///

83 | /// { "Try to drag this area" } 84 | ///

85 | ///
86 | /// } 87 | /// } 88 | /// ``` 89 | #[hook] 90 | pub fn use_drag_with_options(node: NodeRef, options: UseDragOptions) -> UseDragHandle { 91 | let dragging = use_state(|| false); 92 | 93 | let ondragstart_ref = use_mut_latest(options.ondragstart); 94 | let ondragend_ref = use_mut_latest(options.ondragend); 95 | 96 | { 97 | let dragging = dragging.clone(); 98 | use_event(node.clone(), "dragstart", move |e: DragEvent| { 99 | dragging.set(true); 100 | let ondragstart_ref = ondragstart_ref.current(); 101 | let ondragstart = &mut *ondragstart_ref.borrow_mut(); 102 | if let Some(ondragstart) = ondragstart { 103 | ondragstart(e); 104 | } 105 | }); 106 | } 107 | 108 | { 109 | let dragging = dragging.clone(); 110 | use_event(node.clone(), "dragend", move |e: DragEvent| { 111 | dragging.set(false); 112 | let ondragend_ref = ondragend_ref.current(); 113 | let ondragend = &mut *ondragend_ref.borrow_mut(); 114 | if let Some(ondragend) = ondragend { 115 | ondragend(e); 116 | } 117 | }); 118 | } 119 | 120 | use_effect_with(node, move |node| { 121 | if let Some(element) = &node.cast::() { 122 | let _ = element.set_attribute("draggable", "true"); 123 | } 124 | 125 | || () 126 | }); 127 | 128 | UseDragHandle { dragging } 129 | } 130 | -------------------------------------------------------------------------------- /crates/yew-hooks/src/hooks/use_effect_once.rs: -------------------------------------------------------------------------------- 1 | use yew::prelude::*; 2 | 3 | /// A lifecycle hook that runs an effect only once. 4 | /// 5 | /// # Example 6 | /// 7 | /// ```rust 8 | /// # use yew::prelude::*; 9 | /// # use log::debug; 10 | /// # 11 | /// use yew_hooks::prelude::*; 12 | /// 13 | /// #[function_component(EffectOnce)] 14 | /// fn effect_once() -> Html { 15 | /// use_effect_once(|| { 16 | /// debug!("Running effect once on mount"); 17 | /// 18 | /// || debug!("Running clean-up of effect on unmount") 19 | /// }); 20 | /// 21 | /// html! { 22 | /// <> 23 | /// 24 | /// } 25 | /// } 26 | /// ``` 27 | #[hook] 28 | pub fn use_effect_once(callback: Callback) 29 | where 30 | Callback: FnOnce() -> Destructor + 'static, 31 | Destructor: FnOnce() + 'static, 32 | { 33 | use_effect_with((), move |_| callback()); 34 | } 35 | -------------------------------------------------------------------------------- /crates/yew-hooks/src/hooks/use_effect_update.rs: -------------------------------------------------------------------------------- 1 | use yew::prelude::*; 2 | 3 | use super::use_is_first_mount; 4 | 5 | /// This hook ignores the first invocation (e.g. on mount). 6 | /// The signature is exactly the same as the [`use_effect`] hook. 7 | /// 8 | /// # Example 9 | /// 10 | /// ```rust 11 | /// # use yew::prelude::*; 12 | /// # use log::debug; 13 | /// # 14 | /// use yew_hooks::prelude::*; 15 | /// 16 | /// #[function_component(UseEffectUpdate)] 17 | /// fn effect_update() -> Html { 18 | /// use_effect_update(|| { 19 | /// debug!("Running effect only on updates"); 20 | /// 21 | /// || () 22 | /// }); 23 | /// 24 | /// html! { 25 | /// <> 26 | /// 27 | /// } 28 | /// } 29 | /// ``` 30 | #[hook] 31 | pub fn use_effect_update(callback: Callback) 32 | where 33 | Callback: FnOnce() -> Destructor + 'static, 34 | Destructor: FnOnce() + 'static, 35 | { 36 | let first = use_is_first_mount(); 37 | 38 | use_effect(move || { 39 | if first { 40 | Box::new(|| ()) as Box 41 | } else { 42 | Box::new(callback()) 43 | } 44 | }); 45 | } 46 | 47 | /// This hook is similar to [`use_effect_update`] but it accepts dependencies. 48 | /// The signature is exactly the same as the [`use_effect_with`] hook. 49 | #[hook] 50 | pub fn use_effect_update_with_deps( 51 | callback: Callback, 52 | deps: Dependents, 53 | ) where 54 | Callback: FnOnce(&Dependents) -> Destructor + 'static, 55 | Destructor: FnOnce() + 'static, 56 | Dependents: PartialEq + 'static, 57 | { 58 | let first = use_is_first_mount(); 59 | 60 | use_effect_with(deps, move |deps| { 61 | if first { 62 | Box::new(|| ()) as Box 63 | } else { 64 | Box::new(callback(deps)) 65 | } 66 | }); 67 | } 68 | -------------------------------------------------------------------------------- /crates/yew-hooks/src/hooks/use_event.rs: -------------------------------------------------------------------------------- 1 | use std::borrow::Cow; 2 | 3 | use gloo::events::{EventListener, EventListenerOptions}; 4 | use gloo::utils::window; 5 | use wasm_bindgen::JsValue; 6 | use yew::prelude::*; 7 | 8 | use super::use_latest; 9 | 10 | /// A hook that subscribes a callback to events. 11 | /// 12 | /// # Example 13 | /// 14 | /// ```rust 15 | /// # use yew::prelude::*; 16 | /// # use log::debug; 17 | /// # 18 | /// use yew_hooks::prelude::*; 19 | /// 20 | /// #[function_component(UseEvent)] 21 | /// fn event() -> Html { 22 | /// let button = use_node_ref(); 23 | /// 24 | /// use_event(button.clone(), "click", move |_: MouseEvent| { 25 | /// debug!("Clicked!"); 26 | /// }); 27 | /// 28 | /// html! { 29 | /// <> 30 | /// 31 | /// 32 | /// } 33 | /// } 34 | /// ``` 35 | #[hook] 36 | pub fn use_event(node: NodeRef, event_type: T, callback: F) 37 | where 38 | T: Into>, 39 | F: Fn(E) + 'static, 40 | E: From, 41 | { 42 | let callback = use_latest(callback); 43 | 44 | use_effect_with((node, event_type.into()), move |(node, event_type)| { 45 | let window = window(); 46 | let node = node.get(); 47 | // If we cannot get the wrapped `Node`, then we use `Window` as the default target of the event. 48 | let target = node.as_deref().map_or(&*window, |t| t); 49 | 50 | // We should only set passive event listeners for `touchstart` and `touchmove`. 51 | // See here: https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#Improving_scrolling_performance_with_passive_listeners 52 | let listener = 53 | if event_type == "touchstart" || event_type == "touchmove" || event_type == "scroll" { 54 | Some(EventListener::new( 55 | target, 56 | event_type.clone(), 57 | move |event| { 58 | (*callback.current())(JsValue::from(event).into()); 59 | }, 60 | )) 61 | } else { 62 | Some(EventListener::new_with_options( 63 | target, 64 | event_type.clone(), 65 | EventListenerOptions::enable_prevent_default(), 66 | move |event| { 67 | (*callback.current())(JsValue::from(event).into()); 68 | }, 69 | )) 70 | }; 71 | 72 | move || drop(listener) 73 | }); 74 | } 75 | 76 | /// A hook that subscribes a callback to events only for window. 77 | /// If you want to specify an event target, use [`use_event`]. 78 | /// 79 | /// # Example 80 | /// 81 | /// ```rust 82 | /// # use yew::prelude::*; 83 | /// # use log::debug; 84 | /// # 85 | /// use yew_hooks::prelude::*; 86 | /// 87 | /// #[function_component(UseEvent)] 88 | /// fn event() -> Html { 89 | /// use_event_with_window("keypress", move |e: KeyboardEvent| { 90 | /// debug!("{} is pressed!", e.key()); 91 | /// }); 92 | /// 93 | /// html! { 94 | /// <> 95 | /// { "Press any key on your awesome keyboard!" } 96 | /// 97 | /// } 98 | /// } 99 | /// ``` 100 | #[hook] 101 | pub fn use_event_with_window(event_type: T, callback: F) 102 | where 103 | T: Into>, 104 | F: Fn(E) + 'static, 105 | E: From, 106 | { 107 | use_event(NodeRef::default(), event_type, callback); 108 | } 109 | -------------------------------------------------------------------------------- /crates/yew-hooks/src/hooks/use_favicon.rs: -------------------------------------------------------------------------------- 1 | use gloo::utils::document; 2 | use web_sys::{HtmlLinkElement, Node}; 3 | 4 | use wasm_bindgen::{JsCast, UnwrapThrowExt}; 5 | use yew::prelude::*; 6 | 7 | /// A side-effect hook that sets favicon of the page. 8 | /// 9 | /// # Example 10 | /// 11 | /// ```rust 12 | /// # use yew::prelude::*; 13 | /// # 14 | /// use yew_hooks::prelude::*; 15 | /// 16 | /// #[function_component(Favicon)] 17 | /// fn favicon() -> Html { 18 | /// use_favicon("https://crates.io/favicon.ico".to_string()); 19 | /// 20 | /// html! { 21 | /// <> 22 | /// 23 | /// } 24 | /// } 25 | /// ``` 26 | #[hook] 27 | pub fn use_favicon(href: String) { 28 | use_effect_with(href, move |href| { 29 | let link = { 30 | if let Ok(Some(link)) = document().query_selector("link[rel*='icon']") { 31 | link 32 | } else { 33 | document().create_element("link").unwrap_throw() 34 | } 35 | } 36 | .dyn_into::() 37 | .unwrap_throw(); 38 | 39 | link.set_type("image/x-icon"); 40 | link.set_rel("shortcut icon"); 41 | link.set_href(href); 42 | 43 | let head = document() 44 | .get_elements_by_tag_name("head") 45 | .item(0) 46 | .unwrap_throw(); 47 | let _ = head.append_child(&link.dyn_into::().unwrap_throw()); 48 | 49 | || () 50 | }); 51 | } 52 | -------------------------------------------------------------------------------- /crates/yew-hooks/src/hooks/use_hash.rs: -------------------------------------------------------------------------------- 1 | use std::ops::Deref; 2 | 3 | use gloo::utils::window; 4 | use yew::prelude::*; 5 | 6 | use super::use_event_with_window; 7 | 8 | /// State handle for the [`use_hash`] hook. 9 | pub struct UseHashHandle { 10 | inner: UseStateHandle, 11 | } 12 | 13 | impl UseHashHandle { 14 | pub fn set(&self, hash: String) { 15 | if *self.inner != hash { 16 | let _ = window().location().set_hash(hash.as_str()); 17 | } 18 | } 19 | } 20 | 21 | impl Deref for UseHashHandle { 22 | type Target = String; 23 | 24 | fn deref(&self) -> &Self::Target { 25 | &self.inner 26 | } 27 | } 28 | 29 | impl Clone for UseHashHandle { 30 | fn clone(&self) -> Self { 31 | Self { 32 | inner: self.inner.clone(), 33 | } 34 | } 35 | } 36 | 37 | impl PartialEq for UseHashHandle { 38 | fn eq(&self, other: &Self) -> bool { 39 | *self.inner == *other.inner 40 | } 41 | } 42 | 43 | /// A sensor hook that tracks brower's location hash value. 44 | /// 45 | /// # Example 46 | /// 47 | /// ```rust 48 | /// # use yew::prelude::*; 49 | /// # 50 | /// use yew_hooks::prelude::*; 51 | /// 52 | /// #[function_component(UseHash)] 53 | /// fn hash() -> Html { 54 | /// let hash = use_hash(); 55 | /// 56 | /// let onclick = { 57 | /// let hash = hash.clone(); 58 | /// Callback::from(move |_| { 59 | /// hash.set("#/path/to/page?userId=123".to_string()) 60 | /// }) 61 | /// }; 62 | /// 63 | /// html! { 64 | /// <> 65 | /// 66 | ///

67 | /// { " Current hash: " } 68 | /// { &*hash } 69 | ///

70 | /// 71 | /// } 72 | /// } 73 | /// ``` 74 | #[hook] 75 | pub fn use_hash() -> UseHashHandle { 76 | let inner = use_state(|| window().location().hash().unwrap_or_default()); 77 | 78 | { 79 | let inner = inner.clone(); 80 | use_event_with_window("hashchange", move |_: Event| { 81 | inner.set(window().location().hash().unwrap_or_default()); 82 | }); 83 | } 84 | 85 | UseHashHandle { inner } 86 | } 87 | -------------------------------------------------------------------------------- /crates/yew-hooks/src/hooks/use_hovered.rs: -------------------------------------------------------------------------------- 1 | use yew::prelude::*; 2 | 3 | use super::use_event; 4 | 5 | /// A sensor hook that tracks whether HTML element is being hovered. 6 | /// 7 | /// # Example 8 | /// 9 | /// ```rust 10 | /// # use yew::prelude::*; 11 | /// # 12 | /// use yew_hooks::prelude::*; 13 | /// 14 | /// #[function_component(UseHovered)] 15 | /// fn hovered() -> Html { 16 | /// let node = use_node_ref(); 17 | /// let state = use_hovered(node.clone()); 18 | /// 19 | /// html! { 20 | ///
21 | /// { " Hovered: " } 22 | /// { state } 23 | ///
24 | /// } 25 | /// } 26 | /// ``` 27 | #[hook] 28 | pub fn use_hovered(node: NodeRef) -> bool { 29 | let state = use_state_eq(|| false); 30 | 31 | { 32 | let state = state.clone(); 33 | let node = node.clone(); 34 | use_event(node, "mouseover", move |_: Event| { 35 | state.set(true); 36 | }); 37 | } 38 | 39 | { 40 | let state = state.clone(); 41 | use_event(node, "mouseout", move |_: Event| { 42 | state.set(false); 43 | }); 44 | } 45 | 46 | *state 47 | } 48 | -------------------------------------------------------------------------------- /crates/yew-hooks/src/hooks/use_infinite_scroll.rs: -------------------------------------------------------------------------------- 1 | use web_sys::Element; 2 | use yew::prelude::*; 3 | 4 | use super::{use_debounce, use_event, use_latest}; 5 | 6 | /// A sensor hook that tracks infinite scrolling of the element. 7 | /// 8 | /// # Example 9 | /// 10 | /// ```rust 11 | /// # use yew::prelude::*; 12 | /// # 13 | /// use yew_hooks::prelude::*; 14 | /// 15 | /// #[function_component(UseInfiniteScroll)] 16 | /// fn infinite_scroll() -> Html { 17 | /// let node = use_node_ref(); 18 | /// let state = use_list(vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); 19 | /// 20 | /// { 21 | /// let state = state.clone(); 22 | /// use_infinite_scroll(node.clone(), move || { 23 | /// let max = state.current().len() + 1; 24 | /// let mut more = vec![max, max + 1, max + 2, max + 3, max + 4]; 25 | /// state.append(&mut more); 26 | /// }); 27 | /// } 28 | /// 29 | /// html! { 30 | ///
31 | /// { 32 | /// for state.current().iter().map(|element| { 33 | /// html! {

{ element }

} 34 | /// }) 35 | /// } 36 | ///
37 | /// } 38 | /// } 39 | /// ``` 40 | #[hook] 41 | pub fn use_infinite_scroll(node: NodeRef, callback: Callback) 42 | where 43 | Callback: Fn() + 'static, 44 | { 45 | let callback_ref = use_latest(callback); 46 | let load_more = use_state_eq(|| false); 47 | 48 | { 49 | let load_more = load_more.clone(); 50 | use_effect_with(load_more, move |load_more| { 51 | if **load_more { 52 | let callback = &*callback_ref.current(); 53 | callback(); 54 | } 55 | 56 | || () 57 | }); 58 | } 59 | 60 | let debounce = { 61 | let load_more = load_more.clone(); 62 | use_debounce( 63 | move || { 64 | load_more.set(false); 65 | }, 66 | 100, 67 | ) 68 | }; 69 | 70 | use_event(node, "scroll", move |e: Event| { 71 | let element: Element = e.target_unchecked_into(); 72 | if element.scroll_height() - element.scroll_top() <= element.client_height() + 100 { 73 | load_more.set(true); 74 | debounce.run(); 75 | } 76 | }); 77 | } 78 | -------------------------------------------------------------------------------- /crates/yew-hooks/src/hooks/use_interval.rs: -------------------------------------------------------------------------------- 1 | use gloo::timers::callback::Interval; 2 | use yew::prelude::*; 3 | 4 | use super::use_mut_latest; 5 | 6 | /// A hook that schedules an interval to invoke `callback` every `millis` milliseconds. 7 | /// The interval will be cancelled if `millis` is set to 0. 8 | /// 9 | /// # Example 10 | /// 11 | /// ```rust 12 | /// # use yew::prelude::*; 13 | /// # 14 | /// use yew_hooks::prelude::*; 15 | /// 16 | /// #[function_component(Interval)] 17 | /// fn interval() -> Html { 18 | /// let state = use_state(|| 0); 19 | /// 20 | /// { 21 | /// let state = state.clone(); 22 | /// use_interval(move || { 23 | /// state.set(*state + 1); 24 | /// }, 2000); 25 | /// } 26 | /// 27 | /// html! { 28 | /// <> 29 | /// { *state } 30 | /// 31 | /// } 32 | /// } 33 | /// ``` 34 | #[hook] 35 | pub fn use_interval(callback: Callback, millis: u32) 36 | where 37 | Callback: FnMut() + 'static, 38 | { 39 | let callback_ref = use_mut_latest(callback); 40 | let interval_ref = use_mut_ref(|| None); 41 | 42 | use_effect_with(millis, move |millis| { 43 | if *millis > 0 { 44 | *interval_ref.borrow_mut() = Some(Interval::new(*millis, move || { 45 | let callback_ref = callback_ref.current(); 46 | let callback = &mut *callback_ref.borrow_mut(); 47 | callback(); 48 | })); 49 | } else { 50 | *interval_ref.borrow_mut() = None; 51 | } 52 | 53 | move || *interval_ref.borrow_mut() = None 54 | }); 55 | } 56 | -------------------------------------------------------------------------------- /crates/yew-hooks/src/hooks/use_is_first_mount.rs: -------------------------------------------------------------------------------- 1 | use yew::prelude::*; 2 | 3 | /// A hook returns true if component is just mounted (on first render) and false otherwise. 4 | /// 5 | /// # Example 6 | /// 7 | /// ```rust 8 | /// # use yew::prelude::*; 9 | /// # 10 | /// use yew_hooks::prelude::*; 11 | /// 12 | /// #[function_component(IsFirstMount)] 13 | /// fn is_first_mount() -> Html { 14 | /// let is_first = use_is_first_mount(); 15 | /// 16 | /// html! { 17 | /// <> 18 | /// { is_first } 19 | /// 20 | /// } 21 | /// } 22 | /// ``` 23 | #[hook] 24 | pub fn use_is_first_mount() -> bool { 25 | let is_first = use_mut_ref(|| true); 26 | 27 | if *is_first.borrow_mut() { 28 | *is_first.borrow_mut() = false; 29 | 30 | return true; 31 | } 32 | 33 | false 34 | } 35 | -------------------------------------------------------------------------------- /crates/yew-hooks/src/hooks/use_is_mounted.rs: -------------------------------------------------------------------------------- 1 | use std::rc::Rc; 2 | 3 | use yew::prelude::*; 4 | 5 | use super::use_effect_once; 6 | 7 | /// A hook returns true if component is mounted and false otherwise. 8 | /// 9 | /// # Example 10 | /// 11 | /// ```rust 12 | /// # use yew::prelude::*; 13 | /// # 14 | /// use yew_hooks::prelude::*; 15 | /// 16 | /// #[function_component(IsMounted)] 17 | /// fn is_mounted() -> Html { 18 | /// let is_mounted = use_is_mounted(); 19 | /// 20 | /// html! { 21 | /// <> 22 | /// { is_mounted() } 23 | /// 24 | /// } 25 | /// } 26 | /// ``` 27 | #[hook] 28 | pub fn use_is_mounted() -> Rc bool> { 29 | let is_mounted = use_mut_ref(|| false); 30 | 31 | { 32 | let is_mounted = is_mounted.clone(); 33 | 34 | use_effect_once(move || { 35 | *is_mounted.borrow_mut() = true; 36 | 37 | move || *is_mounted.borrow_mut() = false 38 | }); 39 | } 40 | 41 | Rc::new(move || { 42 | let is_mounted = *is_mounted.borrow(); 43 | is_mounted 44 | }) 45 | } 46 | -------------------------------------------------------------------------------- /crates/yew-hooks/src/hooks/use_local_storage.rs: -------------------------------------------------------------------------------- 1 | use std::ops::Deref; 2 | use std::rc::Rc; 3 | 4 | use gloo::storage::{LocalStorage, Storage}; 5 | use serde::{Deserialize, Serialize}; 6 | use web_sys::StorageEvent; 7 | use yew::prelude::*; 8 | 9 | use super::use_event_with_window; 10 | 11 | /// State handle for the [`use_local_storage`] hook. 12 | pub struct UseLocalStorageHandle { 13 | inner: UseStateHandle>, 14 | key: Rc, 15 | } 16 | 17 | impl UseLocalStorageHandle { 18 | /// Set a `value` for the specified key. 19 | pub fn set(&self, value: T) 20 | where 21 | T: Serialize + Clone, 22 | { 23 | if LocalStorage::set(&*self.key, value.clone()).is_ok() { 24 | self.inner.set(Some(value)); 25 | } 26 | } 27 | 28 | /// Delete a key and it's stored value. 29 | pub fn delete(&self) { 30 | LocalStorage::delete(&*self.key); 31 | self.inner.set(None); 32 | } 33 | } 34 | 35 | impl Deref for UseLocalStorageHandle { 36 | type Target = Option; 37 | 38 | fn deref(&self) -> &Self::Target { 39 | &self.inner 40 | } 41 | } 42 | 43 | impl Clone for UseLocalStorageHandle { 44 | fn clone(&self) -> Self { 45 | Self { 46 | inner: self.inner.clone(), 47 | key: self.key.clone(), 48 | } 49 | } 50 | } 51 | 52 | impl PartialEq for UseLocalStorageHandle 53 | where 54 | T: PartialEq, 55 | { 56 | fn eq(&self, other: &Self) -> bool { 57 | *self.inner == *other.inner 58 | } 59 | } 60 | 61 | /// A side-effect hook that manages a single localStorage key. 62 | /// 63 | /// # Example 64 | /// 65 | /// ```rust 66 | /// # use yew::prelude::*; 67 | /// # 68 | /// use yew_hooks::prelude::*; 69 | /// 70 | /// #[function_component(LocalStorage)] 71 | /// fn local_storage() -> Html { 72 | /// let storage = use_local_storage::("foo".to_string()); 73 | /// 74 | /// let onclick = { 75 | /// let storage = storage.clone(); 76 | /// Callback::from(move |_| storage.set("bar".to_string())) 77 | /// }; 78 | /// let ondelete = { 79 | /// let storage = storage.clone(); 80 | /// Callback::from(move |_| storage.delete()) 81 | /// }; 82 | /// 83 | /// html! { 84 | ///
85 | /// 86 | /// 87 | ///

88 | /// { "Current value: " } 89 | /// { 90 | /// if let Some(value) = &*storage { 91 | /// html! { value } 92 | /// } else { 93 | /// html! {} 94 | /// } 95 | /// } 96 | ///

97 | ///
98 | /// } 99 | /// } 100 | /// ``` 101 | #[hook] 102 | pub fn use_local_storage(key: String) -> UseLocalStorageHandle 103 | where 104 | T: for<'de> Deserialize<'de> + 'static, 105 | { 106 | let inner: UseStateHandle> = 107 | use_state(|| LocalStorage::get(&key).unwrap_or_default()); 108 | let key = use_memo((), |_| key); 109 | 110 | { 111 | let key = key.clone(); 112 | let inner = inner.clone(); 113 | use_event_with_window("storage", move |e: StorageEvent| { 114 | if let Some(k) = e.key() { 115 | if k == *key { 116 | inner.set(LocalStorage::get(&*key).unwrap_or_default()); 117 | } 118 | } 119 | }); 120 | } 121 | 122 | UseLocalStorageHandle { inner, key } 123 | } 124 | -------------------------------------------------------------------------------- /crates/yew-hooks/src/hooks/use_location.rs: -------------------------------------------------------------------------------- 1 | use gloo::utils::window; 2 | use wasm_bindgen::JsValue; 3 | use yew::prelude::*; 4 | 5 | use super::use_event_with_window; 6 | 7 | /// State for brower's location. 8 | pub struct LocationState { 9 | pub trigger: String, 10 | pub state: Option, 11 | pub length: u32, 12 | pub hash: String, 13 | pub host: String, 14 | pub hostname: String, 15 | pub href: String, 16 | pub origin: String, 17 | pub pathname: String, 18 | pub port: String, 19 | pub protocol: String, 20 | pub search: String, 21 | } 22 | 23 | /// A sensor hook that tracks brower's location value. 24 | /// 25 | /// # Example 26 | /// 27 | /// ```rust 28 | /// # use yew::prelude::*; 29 | /// # 30 | /// use yew_hooks::prelude::*; 31 | /// 32 | /// #[function_component(UseLocation)] 33 | /// fn location() -> Html { 34 | /// let location = use_location(); 35 | /// 36 | /// html! { 37 | /// <> 38 | ///

39 | /// { "trigger: " } 40 | /// { &location.trigger } 41 | ///

42 | ///

43 | /// { "state: " } 44 | /// { format!("{:?}", &location.state) } 45 | ///

46 | ///

47 | /// { "length: " } 48 | /// { &location.length } 49 | ///

50 | ///

51 | /// { "hash: " } 52 | /// { &location.hash } 53 | ///

54 | ///

55 | /// { "host: " } 56 | /// { &location.host } 57 | ///

58 | ///

59 | /// { "hostname: " } 60 | /// { &location.hostname } 61 | ///

62 | ///

63 | /// { "href: " } 64 | /// { &location.href } 65 | ///

66 | ///

67 | /// { "origin: " } 68 | /// { &location.origin } 69 | ///

70 | ///

71 | /// { "pathname: " } 72 | /// { &location.pathname } 73 | ///

74 | ///

75 | /// { "port: " } 76 | /// { &location.port } 77 | ///

78 | ///

79 | /// { "protocol: " } 80 | /// { &location.protocol } 81 | ///

82 | ///

83 | /// { "search: " } 84 | /// { &location.search } 85 | ///

86 | /// 87 | /// } 88 | /// } 89 | /// ``` 90 | #[hook] 91 | pub fn use_location() -> UseStateHandle { 92 | let state = use_state(|| build_location("load".to_string())); 93 | 94 | { 95 | let state = state.clone(); 96 | use_event_with_window("popstate", move |_: Event| { 97 | state.set(build_location("popstate".to_string())); 98 | }); 99 | } 100 | 101 | { 102 | let state = state.clone(); 103 | use_event_with_window("pushstate", move |_: Event| { 104 | state.set(build_location("pushstate".to_string())); 105 | }); 106 | } 107 | 108 | { 109 | let state = state.clone(); 110 | use_event_with_window("replacestate", move |_: Event| { 111 | state.set(build_location("replacestate".to_string())); 112 | }); 113 | } 114 | 115 | state 116 | } 117 | 118 | fn build_location(trigger: String) -> LocationState { 119 | let location = window().location(); 120 | let history = window().history().map_or((None, 0), |history| { 121 | ( 122 | history.state().ok(), 123 | history.length().map_or(0, |length| length), 124 | ) 125 | }); 126 | 127 | LocationState { 128 | trigger, 129 | state: history.0, 130 | length: history.1, 131 | hash: location.hash().unwrap_or_default(), 132 | host: location.host().unwrap_or_default(), 133 | hostname: location.hostname().unwrap_or_default(), 134 | href: location.href().unwrap_or_default(), 135 | origin: location.origin().unwrap_or_default(), 136 | pathname: location.pathname().unwrap_or_default(), 137 | port: location.port().unwrap_or_default(), 138 | protocol: location.protocol().unwrap_or_default(), 139 | search: location.search().unwrap_or_default(), 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /crates/yew-hooks/src/hooks/use_logger.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::Debug; 2 | use std::rc::Rc; 3 | 4 | use yew::prelude::*; 5 | 6 | use super::{use_effect_once, use_effect_update, use_effect_update_with_deps, use_previous}; 7 | 8 | /// This hook logs in console as component goes through life-cycles. 9 | /// 10 | /// # Example 11 | /// 12 | /// ```rust 13 | /// # use yew::prelude::*; 14 | /// # 15 | /// use yew_hooks::prelude::*; 16 | /// 17 | /// #[function_component(UseLogger)] 18 | /// fn logger(props: &Props) -> Html { 19 | /// use_logger("MyComponent".to_string(), props.clone()); 20 | /// 21 | /// let c = use_state(|| 0); 22 | /// let d = use_state(|| "d".to_string()); 23 | /// use_logger("MyComponent".to_string(), (c.clone(), d.clone())); 24 | /// 25 | /// html! { 26 | /// <> 27 | /// { " a: " } { props.a } 28 | /// { " b: " } { &props.b } 29 | /// { " c: " } { *c } 30 | /// { " d: " } { &*d } 31 | /// 32 | /// } 33 | /// } 34 | /// 35 | /// #[derive(Debug, Properties, PartialEq, Clone)] 36 | /// struct Props { 37 | /// pub a: i32, 38 | /// pub b: String, 39 | /// } 40 | /// ``` 41 | #[hook] 42 | pub fn use_logger(name: String, props: T) 43 | where 44 | T: Debug + 'static, 45 | { 46 | let name = Rc::new(name); 47 | let props = Rc::new(props); 48 | 49 | { 50 | let name = name.clone(); 51 | let props = props.clone(); 52 | 53 | use_effect_once(move || { 54 | log::debug!("{} mounted: {:?}", name, props); 55 | 56 | move || log::debug!("{} unmounted", name) 57 | }); 58 | } 59 | 60 | use_effect_update(move || { 61 | log::debug!("{} updated: {:?}", name, props); 62 | || () 63 | }); 64 | } 65 | 66 | /// This hook logs in console as component goes through life-cycles. 67 | /// Like [`use_logger`] but only logs when `prev_state != next_state`. 68 | /// This requires the props to implement [`PartialEq`]. 69 | /// 70 | /// # Example 71 | /// 72 | /// ```rust 73 | /// # use yew::prelude::*; 74 | /// # 75 | /// use yew_hooks::prelude::*; 76 | /// 77 | /// #[function_component(UseLogger)] 78 | /// fn logger(props: &Props) -> Html { 79 | /// use_logger_eq("MyComponent".to_string(), props.clone()); 80 | /// 81 | /// let c = use_state(|| 0); 82 | /// let d = use_state(|| "d".to_string()); 83 | /// use_logger_eq("MyComponent".to_string(), (c.clone(), d.clone())); 84 | /// 85 | /// html! { 86 | /// <> 87 | /// { " a: " } { props.a } 88 | /// { " b: " } { &props.b } 89 | /// { " c: " } { *c } 90 | /// { " d: " } { &*d } 91 | /// 92 | /// } 93 | /// } 94 | /// 95 | /// #[derive(Debug, Properties, PartialEq, Clone)] 96 | /// struct Props { 97 | /// pub a: i32, 98 | /// pub b: String, 99 | /// } 100 | /// ``` 101 | #[hook] 102 | pub fn use_logger_eq(name: String, props: T) 103 | where 104 | T: Debug + PartialEq + 'static, 105 | { 106 | let name = Rc::new(name); 107 | let props = Rc::new(props); 108 | let props_prev = use_previous(props.clone()); 109 | 110 | { 111 | let name = name.clone(); 112 | let props = props.clone(); 113 | 114 | use_effect_once(move || { 115 | log::debug!("{} mounted: {:?}", name, props); 116 | 117 | move || log::debug!("{} unmounted", name) 118 | }); 119 | } 120 | 121 | use_effect_update_with_deps( 122 | move |props| { 123 | log::debug!("{} updated: from {:?}, to {:?}", name, *props_prev, props); 124 | || () 125 | }, 126 | props, 127 | ); 128 | } 129 | -------------------------------------------------------------------------------- /crates/yew-hooks/src/hooks/use_measure.rs: -------------------------------------------------------------------------------- 1 | use wasm_bindgen::{prelude::*, JsCast}; 2 | use web_sys::Element; 3 | use yew::prelude::*; 4 | 5 | use super::use_raf_state; 6 | use crate::web_sys_ext::{ResizeObserver, ResizeObserverEntry}; 7 | 8 | #[derive(PartialEq, Default, Clone)] 9 | pub struct UseMeasureState { 10 | pub x: f64, 11 | pub y: f64, 12 | pub width: f64, 13 | pub height: f64, 14 | pub top: f64, 15 | pub left: f64, 16 | pub bottom: f64, 17 | pub right: f64, 18 | } 19 | 20 | /// A sensor hook that tracks an HTML element's dimensions using the `ResizeObserver` API. 21 | /// 22 | /// # Example 23 | /// 24 | /// ```rust 25 | /// # use yew::prelude::*; 26 | /// # 27 | /// use yew_hooks::prelude::*; 28 | /// 29 | /// #[function_component(UseMeasure)] 30 | /// fn measure() -> Html { 31 | /// let node = use_node_ref(); 32 | /// let state = use_measure(node.clone()); 33 | /// 34 | /// html! { 35 | ///
36 | /// { " X: " } 37 | /// { state.x } 38 | /// { " Y: " } 39 | /// { state.y } 40 | /// { " Width: " } 41 | /// { state.width } 42 | /// { " Height: " } 43 | /// { state.height } 44 | /// { " Top: " } 45 | /// { state.top } 46 | /// { " Left: " } 47 | /// { state.left } 48 | /// { " Bottom: " } 49 | /// { state.bottom } 50 | /// { " Right: " } 51 | /// { state.right } 52 | ///
53 | /// } 54 | /// } 55 | /// ``` 56 | #[hook] 57 | pub fn use_measure(node: NodeRef) -> UseMeasureState { 58 | let state = use_raf_state(UseMeasureState::default); 59 | 60 | { 61 | let state = state.clone(); 62 | use_effect_with(node, move |node| { 63 | let closure = Closure::wrap(Box::new(move |entries: Vec| { 64 | for entry in &entries { 65 | let rect = entry.content_rect(); 66 | state.set(UseMeasureState { 67 | x: rect.x(), 68 | y: rect.y(), 69 | width: rect.width(), 70 | height: rect.height(), 71 | top: rect.top(), 72 | left: rect.left(), 73 | bottom: rect.bottom(), 74 | right: rect.right(), 75 | }); 76 | } 77 | }) as Box)>); 78 | 79 | let observer = ResizeObserver::new(closure.as_ref().unchecked_ref()).unwrap_throw(); 80 | // Forget the closure to keep it alive 81 | closure.forget(); 82 | 83 | if let Some(element) = &node.cast::() { 84 | observer.observe(element); 85 | } 86 | 87 | move || observer.disconnect() 88 | }); 89 | } 90 | 91 | (*state).clone() 92 | } 93 | -------------------------------------------------------------------------------- /crates/yew-hooks/src/hooks/use_mount.rs: -------------------------------------------------------------------------------- 1 | use yew::prelude::*; 2 | 3 | use super::use_effect_once; 4 | 5 | /// A lifecycle hook that calls a function after the component is mounted. 6 | /// 7 | /// # Example 8 | /// 9 | /// ```rust 10 | /// # use yew::prelude::*; 11 | /// # use log::debug; 12 | /// # 13 | /// use yew_hooks::prelude::*; 14 | /// 15 | /// #[function_component(Mount)] 16 | /// fn mount() -> Html { 17 | /// use_mount(|| { 18 | /// debug!("Running effect once on mount"); 19 | /// }); 20 | /// 21 | /// html! { 22 | /// <> 23 | /// 24 | /// } 25 | /// } 26 | /// ``` 27 | #[hook] 28 | pub fn use_mount(callback: Callback) 29 | where 30 | Callback: FnOnce() + 'static, 31 | { 32 | use_effect_once(move || { 33 | callback(); 34 | 35 | || () 36 | }); 37 | } 38 | -------------------------------------------------------------------------------- /crates/yew-hooks/src/hooks/use_previous.rs: -------------------------------------------------------------------------------- 1 | use std::ops::Deref; 2 | use std::rc::Rc; 3 | 4 | use yew::prelude::*; 5 | 6 | /// State handle for the [`use_previous`] hook. 7 | pub struct UsePreviousHandle { 8 | inner: Rc, 9 | } 10 | 11 | impl UsePreviousHandle { 12 | /// Get the previous immutable ref to state or props. 13 | pub fn previous(&self) -> Rc { 14 | self.inner.clone() 15 | } 16 | } 17 | 18 | impl Deref for UsePreviousHandle { 19 | type Target = T; 20 | 21 | fn deref(&self) -> &Self::Target { 22 | &self.inner 23 | } 24 | } 25 | 26 | impl Clone for UsePreviousHandle { 27 | fn clone(&self) -> Self { 28 | Self { 29 | inner: self.inner.clone(), 30 | } 31 | } 32 | } 33 | 34 | impl PartialEq for UsePreviousHandle 35 | where 36 | T: PartialEq, 37 | { 38 | fn eq(&self, other: &Self) -> bool { 39 | *self.inner == *other.inner 40 | } 41 | } 42 | 43 | /// This hook returns the previous immutable ref to state or props. 44 | /// 45 | /// # Example 46 | /// 47 | /// ```rust 48 | /// # use yew::prelude::*; 49 | /// # 50 | /// use yew_hooks::prelude::*; 51 | /// 52 | /// #[function_component(UsePrevious)] 53 | /// fn previous() -> Html { 54 | /// let state = use_state(|| 0); 55 | /// let previous_state = use_previous(state.clone()); 56 | /// 57 | /// 58 | /// let onincrease = { 59 | /// let state = state.clone(); 60 | /// Callback::from(move |_| state.set(*state + 1)) 61 | /// }; 62 | /// let ondecrease = { 63 | /// let state = state.clone(); 64 | /// Callback::from(move |_| state.set(*state - 1)) 65 | /// }; 66 | /// 67 | /// html! { 68 | ///
69 | /// 70 | /// 71 | ///

72 | /// { "Current value: " } 73 | /// { *state } 74 | ///

75 | ///

76 | /// { "Previous value: " } 77 | /// { **previous_state } 78 | ///

79 | ///
80 | /// } 81 | /// } 82 | /// ``` 83 | #[hook] 84 | pub fn use_previous(value: T) -> UsePreviousHandle 85 | where 86 | T: 'static, 87 | { 88 | let value_rc = Rc::new(value); 89 | let state = use_mut_ref(|| value_rc.clone()); 90 | 91 | // Update the ref each render so if it changes the newest value will be saved. 92 | let inner = state.replace(value_rc); 93 | 94 | UsePreviousHandle { inner } 95 | } 96 | -------------------------------------------------------------------------------- /crates/yew-hooks/src/hooks/use_raf.rs: -------------------------------------------------------------------------------- 1 | use std::cmp::min_by; 2 | 3 | use gloo::render::request_animation_frame; 4 | use gloo::timers::callback::Timeout; 5 | use yew::prelude::*; 6 | 7 | /// An animation hook that forces component to re-render on each `requestAnimationFrame`, 8 | /// returns percentage of time elapsed. `millis` - milliseconds for how long to keep re-rendering component. 9 | /// `delay` — delay in milliseconds after which to start re-rendering component. 10 | /// 11 | /// # Example 12 | /// 13 | /// ```rust 14 | /// # use yew::prelude::*; 15 | /// # 16 | /// use yew_hooks::prelude::*; 17 | /// 18 | /// #[function_component(UseRaf)] 19 | /// fn raf() -> Html { 20 | /// let elapsed = use_raf(5000, 1000); 21 | /// 22 | /// html! { 23 | /// <> 24 | /// { elapsed } 25 | /// 26 | /// } 27 | /// } 28 | /// ``` 29 | #[hook] 30 | pub fn use_raf(millis: u32, delay: u32) -> f64 { 31 | let elapsed = use_state(|| 0f64); 32 | let start = use_mut_ref(|| 0f64); 33 | let raf = use_mut_ref(|| None); 34 | let on_frame = use_mut_ref(|| None); 35 | let on_frame_clone = on_frame.clone(); 36 | let timer_stop = use_mut_ref(|| None); 37 | let timer_delay = use_mut_ref(|| None); 38 | 39 | { 40 | let elapsed = elapsed.clone(); 41 | use_effect_with((millis, delay), move |(millis, delay)| { 42 | let millis = *millis; 43 | let delay = *delay; 44 | *start.borrow_mut() = 0f64; 45 | 46 | { 47 | let raf = raf.clone(); 48 | let elapsed = elapsed.clone(); 49 | *on_frame_clone.borrow_mut() = Some(Box::new(move |time: f64| { 50 | let on_frame = on_frame.clone(); 51 | if *start.borrow() <= 0f64 { 52 | *start.borrow_mut() = time; 53 | } 54 | let time = min_by( 55 | 1f64, 56 | (time - *start.borrow()) / f64::from(millis), 57 | |x, y| x.partial_cmp(y).unwrap(), 58 | ); 59 | elapsed.set(time); 60 | 61 | // Schedule ourself for another requestAnimationFrame callback. 62 | // Ref: https://github.com/rustwasm/wasm-bindgen/blob/main/examples/request-animation-frame/src/lib.rs 63 | *raf.borrow_mut() = Some(request_animation_frame(move |time| { 64 | let on_frame = on_frame.borrow(); 65 | #[allow(clippy::borrowed_box)] 66 | let on_frame: &Box = on_frame.as_ref().unwrap(); 67 | on_frame(time); 68 | })); 69 | }) as Box); 70 | } 71 | 72 | { 73 | let raf = raf.clone(); 74 | let timer_stop = timer_stop.clone(); 75 | *timer_delay.borrow_mut() = Some(Timeout::new(delay, move || { 76 | { 77 | let raf = raf.clone(); 78 | *timer_stop.borrow_mut() = Some(Timeout::new(millis, move || { 79 | *raf.borrow_mut() = None; 80 | elapsed.set(1f64); 81 | })); 82 | } 83 | 84 | *raf.borrow_mut() = Some(request_animation_frame(move |time| { 85 | let on_frame_clone = on_frame_clone.borrow(); 86 | #[allow(clippy::borrowed_box)] 87 | let on_frame_clone: &Box = on_frame_clone.as_ref().unwrap(); 88 | on_frame_clone(time); 89 | })); 90 | })); 91 | } 92 | 93 | move || { 94 | *raf.borrow_mut() = None; 95 | *timer_stop.borrow_mut() = None; 96 | *timer_delay.borrow_mut() = None; 97 | } 98 | }); 99 | } 100 | 101 | *elapsed 102 | } 103 | -------------------------------------------------------------------------------- /crates/yew-hooks/src/hooks/use_raf_state.rs: -------------------------------------------------------------------------------- 1 | use std::ops::Deref; 2 | use std::{cell::RefCell, rc::Rc}; 3 | 4 | use gloo::render::{request_animation_frame, AnimationFrame}; 5 | use yew::prelude::*; 6 | 7 | use super::use_unmount; 8 | 9 | /// State handle for the [`use_raf_state`] hook. 10 | pub struct UseRafStateHandle { 11 | inner: UseStateHandle, 12 | raf: Rc>>, 13 | } 14 | 15 | impl UseRafStateHandle 16 | where 17 | T: 'static, 18 | { 19 | /// Replaces the value. 20 | pub fn set(&self, value: T) { 21 | let inner = self.inner.clone(); 22 | *self.raf.borrow_mut() = Some(request_animation_frame(move |_| { 23 | inner.set(value); 24 | })); 25 | } 26 | } 27 | 28 | impl Deref for UseRafStateHandle { 29 | type Target = T; 30 | 31 | fn deref(&self) -> &Self::Target { 32 | &self.inner 33 | } 34 | } 35 | 36 | impl Clone for UseRafStateHandle { 37 | fn clone(&self) -> Self { 38 | Self { 39 | inner: self.inner.clone(), 40 | raf: self.raf.clone(), 41 | } 42 | } 43 | } 44 | 45 | impl PartialEq for UseRafStateHandle 46 | where 47 | T: PartialEq, 48 | { 49 | fn eq(&self, other: &Self) -> bool { 50 | *self.inner == *other.inner 51 | } 52 | } 53 | 54 | /// A state hook that only updates state in the callback of `requestAnimationFrame`. 55 | /// 56 | /// # Example 57 | /// 58 | /// ```rust 59 | /// # use web_sys::Window; 60 | /// # use yew::prelude::*; 61 | /// # 62 | /// use yew_hooks::prelude::*; 63 | /// 64 | /// #[function_component(UseRafState)] 65 | /// fn raf_state() -> Html { 66 | /// let state = use_raf_state(|| (0f64, 0f64)); 67 | /// 68 | /// { 69 | /// let state = state.clone(); 70 | /// use_event_with_window("resize", move |e: Event| { 71 | /// let window: Window = e.target_unchecked_into(); 72 | /// state.set(( 73 | /// window.inner_width().unwrap().as_f64().unwrap(), 74 | /// window.inner_height().unwrap().as_f64().unwrap(), 75 | /// )); 76 | /// }); 77 | /// } 78 | /// 79 | /// html! { 80 | /// <> 81 | /// { " Width: " } 82 | /// { state.0 } 83 | /// { " Height: " } 84 | /// { state.1 } 85 | /// 86 | /// } 87 | /// } 88 | /// ``` 89 | #[hook] 90 | pub fn use_raf_state(init_fn: F) -> UseRafStateHandle 91 | where 92 | T: 'static, 93 | F: FnOnce() -> T, 94 | { 95 | let inner = use_state(init_fn); 96 | let raf = use_mut_ref(|| None); 97 | 98 | { 99 | let raf = raf.clone(); 100 | use_unmount(move || { 101 | *raf.borrow_mut() = None; 102 | }); 103 | } 104 | 105 | UseRafStateHandle { inner, raf } 106 | } 107 | -------------------------------------------------------------------------------- /crates/yew-hooks/src/hooks/use_renders_count.rs: -------------------------------------------------------------------------------- 1 | use yew::prelude::*; 2 | 3 | /// A hook that counts component renders. 4 | /// 5 | /// # Example 6 | /// 7 | /// ```rust 8 | /// # use yew::prelude::*; 9 | /// # 10 | /// use yew_hooks::prelude::*; 11 | /// 12 | /// #[function_component(Update)] 13 | /// fn update() -> Html { 14 | /// let count = use_renders_count(); 15 | /// let update = use_update(); 16 | /// 17 | /// let onclick = Callback::from(move |_| { 18 | /// update(); 19 | /// }); 20 | /// 21 | /// html! { 22 | /// <> 23 | /// 24 | /// { count } 25 | /// 26 | /// } 27 | /// } 28 | /// ``` 29 | #[hook] 30 | pub fn use_renders_count() -> i32 { 31 | let count = use_mut_ref(|| 0); 32 | 33 | let current = *count.borrow(); 34 | *count.borrow_mut() = current + 1; 35 | let current = *count.borrow(); 36 | 37 | current 38 | } 39 | -------------------------------------------------------------------------------- /crates/yew-hooks/src/hooks/use_scroll.rs: -------------------------------------------------------------------------------- 1 | use web_sys::Element; 2 | use yew::prelude::*; 3 | 4 | use super::{use_event, use_mount}; 5 | 6 | /// A sensor hook that tracks an HTML element's scroll position. 7 | /// 8 | /// # Example 9 | /// 10 | /// ```rust 11 | /// # use yew::prelude::*; 12 | /// # 13 | /// use yew_hooks::prelude::*; 14 | /// 15 | /// #[function_component(UseScroll)] 16 | /// fn scroll() -> Html { 17 | /// let node = use_node_ref(); 18 | /// let state = use_scroll(node.clone()); 19 | /// 20 | /// html! { 21 | ///
22 | /// { " X: " } 23 | /// { state.0 } 24 | /// { " Y: " } 25 | /// { state.1 } 26 | ///
27 | /// } 28 | /// } 29 | /// ``` 30 | #[hook] 31 | pub fn use_scroll(node: NodeRef) -> (i32, i32) { 32 | let state = use_state(|| (0, 0)); 33 | 34 | { 35 | let state = state.clone(); 36 | let node = node.clone(); 37 | use_event(node, "scroll", move |e: Event| { 38 | let element: Element = e.target_unchecked_into(); 39 | state.set((element.scroll_left(), element.scroll_top())); 40 | }); 41 | } 42 | 43 | { 44 | let state = state.clone(); 45 | use_mount(move || { 46 | if let Some(element) = node.cast::() { 47 | state.set((element.scroll_left(), element.scroll_top())); 48 | } 49 | }); 50 | } 51 | 52 | *state 53 | } 54 | -------------------------------------------------------------------------------- /crates/yew-hooks/src/hooks/use_scrolling.rs: -------------------------------------------------------------------------------- 1 | use yew::prelude::*; 2 | 3 | use super::{use_debounce, use_event}; 4 | 5 | /// A sensor hook that tracks whether HTML element is scrolling. 6 | /// 7 | /// # Example 8 | /// 9 | /// ```rust 10 | /// # use yew::prelude::*; 11 | /// # 12 | /// use yew_hooks::prelude::*; 13 | /// 14 | /// #[function_component(UseScrolling)] 15 | /// fn scrolling() -> Html { 16 | /// let node = use_node_ref(); 17 | /// let state = use_scrolling(node.clone()); 18 | /// 19 | /// html! { 20 | ///
21 | /// { " Scrolling: " } 22 | /// { state } 23 | ///
24 | /// } 25 | /// } 26 | /// ``` 27 | #[hook] 28 | pub fn use_scrolling(node: NodeRef) -> bool { 29 | let state = use_state_eq(|| false); 30 | 31 | let debounce = { 32 | let state = state.clone(); 33 | use_debounce( 34 | move || { 35 | state.set(false); 36 | }, 37 | 150, 38 | ) 39 | }; 40 | 41 | { 42 | let state = state.clone(); 43 | use_event(node, "scroll", move |_: Event| { 44 | state.set(true); 45 | debounce.run(); 46 | }); 47 | } 48 | 49 | *state 50 | } 51 | -------------------------------------------------------------------------------- /crates/yew-hooks/src/hooks/use_search_param.rs: -------------------------------------------------------------------------------- 1 | use gloo::utils::window; 2 | use web_sys::UrlSearchParams; 3 | use yew::prelude::*; 4 | 5 | use super::use_event_with_window; 6 | 7 | /// A sensor hook that tracks brower's location search param value. 8 | /// 9 | /// # Example 10 | /// 11 | /// ```rust 12 | /// # use yew::prelude::*; 13 | /// # 14 | /// use yew_hooks::prelude::*; 15 | /// 16 | /// #[function_component(UseSearchParam)] 17 | /// fn search_param() -> Html { 18 | /// let param = use_search_param("foo".to_string()); 19 | /// 20 | /// html! { 21 | /// <> 22 | ///

23 | /// { " Current search param: " } 24 | /// { param.unwrap_or_default() } 25 | ///

26 | /// 27 | /// } 28 | /// } 29 | /// ``` 30 | #[hook] 31 | pub fn use_search_param(param: String) -> Option { 32 | let state = use_state(|| get_param(param.clone())); 33 | 34 | { 35 | let state = state.clone(); 36 | let param = param.clone(); 37 | use_event_with_window("popstate", move |_: Event| { 38 | let param = param.clone(); 39 | state.set(get_param(param)); 40 | }); 41 | } 42 | 43 | { 44 | let state = state.clone(); 45 | let param = param.clone(); 46 | use_event_with_window("pushstate", move |_: Event| { 47 | let param = param.clone(); 48 | state.set(get_param(param)); 49 | }); 50 | } 51 | 52 | { 53 | let state = state.clone(); 54 | use_event_with_window("replacestate", move |_: Event| { 55 | let param = param.clone(); 56 | state.set(get_param(param)); 57 | }); 58 | } 59 | 60 | (*state).clone() 61 | } 62 | 63 | fn get_param(param: String) -> Option { 64 | let search = window().location().search().unwrap_or_default(); 65 | UrlSearchParams::new_with_str(search.as_str()).map_or(None, |params| params.get(param.as_str())) 66 | } 67 | -------------------------------------------------------------------------------- /crates/yew-hooks/src/hooks/use_session_storage.rs: -------------------------------------------------------------------------------- 1 | use std::ops::Deref; 2 | use std::rc::Rc; 3 | 4 | use gloo::storage::{SessionStorage, Storage}; 5 | use serde::{Deserialize, Serialize}; 6 | use yew::prelude::*; 7 | 8 | /// State handle for the [`use_session_storage`] hook. 9 | pub struct UseSessionStorageHandle { 10 | inner: UseStateHandle>, 11 | key: Rc, 12 | } 13 | 14 | impl UseSessionStorageHandle { 15 | /// Set a `value` for the specified key. 16 | pub fn set(&self, value: T) 17 | where 18 | T: Serialize + Clone, 19 | { 20 | if SessionStorage::set(&*self.key, value.clone()).is_ok() { 21 | self.inner.set(Some(value)); 22 | } 23 | } 24 | 25 | /// Delete a key and it's stored value. 26 | pub fn delete(&self) { 27 | SessionStorage::delete(&*self.key); 28 | self.inner.set(None); 29 | } 30 | } 31 | 32 | impl Deref for UseSessionStorageHandle { 33 | type Target = Option; 34 | 35 | fn deref(&self) -> &Self::Target { 36 | &self.inner 37 | } 38 | } 39 | 40 | impl Clone for UseSessionStorageHandle { 41 | fn clone(&self) -> Self { 42 | Self { 43 | inner: self.inner.clone(), 44 | key: self.key.clone(), 45 | } 46 | } 47 | } 48 | 49 | impl PartialEq for UseSessionStorageHandle 50 | where 51 | T: PartialEq, 52 | { 53 | fn eq(&self, other: &Self) -> bool { 54 | *self.inner == *other.inner 55 | } 56 | } 57 | 58 | /// A side-effect hook that manages a single sessionStorage key. 59 | /// 60 | /// # Example 61 | /// 62 | /// ```rust 63 | /// # use yew::prelude::*; 64 | /// # 65 | /// use yew_hooks::prelude::*; 66 | /// 67 | /// #[function_component(SessionStorage)] 68 | /// fn session_storage() -> Html { 69 | /// let storage = use_session_storage::("foo".to_string()); 70 | /// 71 | /// let onclick = { 72 | /// let storage = storage.clone(); 73 | /// Callback::from(move |_| storage.set("bar".to_string())) 74 | /// }; 75 | /// let ondelete = { 76 | /// let storage = storage.clone(); 77 | /// Callback::from(move |_| storage.delete()) 78 | /// }; 79 | /// 80 | /// html! { 81 | ///
82 | /// 83 | /// 84 | ///

85 | /// { "Current value: " } 86 | /// { 87 | /// if let Some(value) = &*storage { 88 | /// html! { value } 89 | /// } else { 90 | /// html! {} 91 | /// } 92 | /// } 93 | ///

94 | ///
95 | /// } 96 | /// } 97 | /// ``` 98 | #[hook] 99 | pub fn use_session_storage(key: String) -> UseSessionStorageHandle 100 | where 101 | T: for<'de> Deserialize<'de> + 'static, 102 | { 103 | let inner: UseStateHandle> = 104 | use_state(|| SessionStorage::get(&key).unwrap_or_default()); 105 | let key = use_memo((), |_| key); 106 | 107 | UseSessionStorageHandle { inner, key } 108 | } 109 | -------------------------------------------------------------------------------- /crates/yew-hooks/src/hooks/use_size.rs: -------------------------------------------------------------------------------- 1 | use wasm_bindgen::{prelude::*, JsCast}; 2 | use web_sys::Element; 3 | use yew::prelude::*; 4 | 5 | use super::use_raf_state; 6 | use crate::web_sys_ext::{ResizeObserver, ResizeObserverEntry}; 7 | 8 | /// A sensor hook that tracks an HTML element's dimensions using the `ResizeObserver` API. 9 | /// 10 | /// # Example 11 | /// 12 | /// ```rust 13 | /// # use yew::prelude::*; 14 | /// # 15 | /// use yew_hooks::prelude::*; 16 | /// 17 | /// #[function_component(UseSize)] 18 | /// fn size() -> Html { 19 | /// let node = use_node_ref(); 20 | /// let state = use_size(node.clone()); 21 | /// 22 | /// html! { 23 | ///
24 | /// { " Width: " } 25 | /// { state.0 } 26 | /// { " Height: " } 27 | /// { state.1 } 28 | ///
29 | /// } 30 | /// } 31 | /// ``` 32 | #[hook] 33 | pub fn use_size(node: NodeRef) -> (u32, u32) { 34 | let state = use_raf_state(|| (0, 0)); 35 | 36 | { 37 | let state = state.clone(); 38 | use_effect_with(node, move |node| { 39 | let closure = Closure::wrap(Box::new(move |entries: Vec| { 40 | for entry in &entries { 41 | let element = entry.target(); 42 | state.set(( 43 | element.client_width() as u32, 44 | element.client_height() as u32, 45 | )); 46 | } 47 | }) as Box)>); 48 | 49 | let observer = ResizeObserver::new(closure.as_ref().unchecked_ref()).unwrap_throw(); 50 | // Forget the closure to keep it alive 51 | closure.forget(); 52 | 53 | if let Some(element) = &node.cast::() { 54 | observer.observe(element); 55 | } 56 | 57 | move || observer.disconnect() 58 | }); 59 | } 60 | 61 | *state 62 | } 63 | -------------------------------------------------------------------------------- /crates/yew-hooks/src/hooks/use_state_ptr_eq.rs: -------------------------------------------------------------------------------- 1 | use std::ops::Deref; 2 | use std::rc::Rc; 3 | 4 | use yew::prelude::*; 5 | 6 | struct UseStatePtrEqReducer { 7 | value: Rc, 8 | } 9 | 10 | impl Reducible for UseStatePtrEqReducer { 11 | type Action = T; 12 | fn reduce(self: Rc, action: Self::Action) -> Rc { 13 | Rc::new(Self { 14 | value: action.into(), 15 | }) 16 | } 17 | } 18 | 19 | impl PartialEq for UseStatePtrEqReducer { 20 | fn eq(&self, rhs: &Self) -> bool { 21 | // Check if the two `Rc`s point to the same allocation, instead of PartialEq of the values. 22 | Rc::ptr_eq(&self.value, &rhs.value) 23 | } 24 | } 25 | 26 | /// State handle for the [`use_state_ptr_eq`] hook. 27 | pub struct UseStatePtrEqHandle { 28 | inner: UseReducerHandle>, 29 | } 30 | 31 | impl UseStatePtrEqHandle { 32 | /// Replaces the value 33 | pub fn set(&self, value: T) { 34 | self.inner.dispatch(value); 35 | } 36 | } 37 | 38 | impl Deref for UseStatePtrEqHandle { 39 | type Target = T; 40 | 41 | fn deref(&self) -> &Self::Target { 42 | &(*self.inner).value 43 | } 44 | } 45 | 46 | impl Clone for UseStatePtrEqHandle { 47 | fn clone(&self) -> Self { 48 | Self { 49 | inner: self.inner.clone(), 50 | } 51 | } 52 | } 53 | 54 | impl PartialEq for UseStatePtrEqHandle 55 | where 56 | T: PartialEq, 57 | { 58 | fn eq(&self, rhs: &Self) -> bool { 59 | *self.inner == *rhs.inner 60 | } 61 | } 62 | 63 | /// Similar to `use_state_eq`, but check if the two `Rc`s of values point to the same allocation, 64 | /// instead of PartialEq of the values. 65 | /// 66 | /// # Example 67 | /// 68 | /// ```rust 69 | /// # use yew::prelude::*; 70 | /// # 71 | /// use yew_hooks::prelude::*; 72 | /// 73 | /// #[function_component(UseStatePtrEq)] 74 | /// fn state_ptr_eq() -> Html { 75 | /// let state = use_state_ptr_eq(|| "".to_string()); 76 | /// 77 | /// let onclick = { 78 | /// let state = state.clone(); 79 | /// Callback::from(move |_| { 80 | /// state.set("Hello, world!".to_string()); 81 | /// }) 82 | /// }; 83 | /// 84 | /// html! { 85 | /// <> 86 | /// 87 | ///

88 | /// { "Current value: " } 89 | /// { &*state } 90 | ///

91 | /// 92 | /// } 93 | /// } 94 | /// ``` 95 | #[hook] 96 | pub fn use_state_ptr_eq(init_fn: F) -> UseStatePtrEqHandle 97 | where 98 | T: 'static, 99 | F: FnOnce() -> T, 100 | { 101 | let handle = use_reducer_eq(move || UseStatePtrEqReducer { 102 | value: Rc::new(init_fn()), 103 | }); 104 | 105 | UseStatePtrEqHandle { inner: handle } 106 | } 107 | -------------------------------------------------------------------------------- /crates/yew-hooks/src/hooks/use_throttle.rs: -------------------------------------------------------------------------------- 1 | use std::rc::Rc; 2 | 3 | use yew::prelude::*; 4 | 5 | use super::{use_mut_latest, use_timeout}; 6 | 7 | /// State handle for the [`use_throttle`] hook. 8 | pub struct UseThrottleHandle { 9 | run: Rc, 10 | cancel: Rc, 11 | } 12 | 13 | impl UseThrottleHandle { 14 | /// Run the throttle. 15 | pub fn run(&self) { 16 | (self.run)(); 17 | } 18 | 19 | /// Cancel the throttle. 20 | pub fn cancel(&self) { 21 | (self.cancel)(); 22 | } 23 | } 24 | 25 | impl Clone for UseThrottleHandle { 26 | fn clone(&self) -> Self { 27 | Self { 28 | run: self.run.clone(), 29 | cancel: self.cancel.clone(), 30 | } 31 | } 32 | } 33 | 34 | /// A hook that throttles invoking a function, the function is only executed once every `millis`. 35 | /// 36 | /// # Example 37 | /// 38 | /// ```rust 39 | /// # use yew::prelude::*; 40 | /// # 41 | /// use yew_hooks::prelude::*; 42 | /// 43 | /// #[function_component(Throttle)] 44 | /// fn throttle() -> Html { 45 | /// let state = use_state(|| 0); 46 | /// 47 | /// let throttle = { 48 | /// let state = state.clone(); 49 | /// use_throttle( 50 | /// move || { 51 | /// state.set(*state + 1); 52 | /// }, 53 | /// 2000, 54 | /// ) 55 | /// }; 56 | /// 57 | /// let onclick = { 58 | /// let throttle = throttle.clone(); 59 | /// Callback::from(move |_| throttle.run()) 60 | /// }; 61 | /// 62 | /// let oncancel = { Callback::from(move |_| throttle.cancel()) }; 63 | /// 64 | /// html! { 65 | /// <> 66 | /// 67 | /// 68 | /// { "State: " } {*state} 69 | /// 70 | /// } 71 | /// } 72 | /// ``` 73 | #[hook] 74 | pub fn use_throttle(callback: Callback, millis: u32) -> UseThrottleHandle 75 | where 76 | Callback: FnMut() + 'static, 77 | { 78 | let throttled = use_mut_ref(|| false); 79 | let callback_ref = use_mut_latest(callback); 80 | let timeout = { 81 | let throttled = throttled.clone(); 82 | use_timeout( 83 | move || { 84 | *throttled.borrow_mut() = false; 85 | }, 86 | millis, 87 | ) 88 | }; 89 | 90 | let run = { 91 | let throttled = throttled.clone(); 92 | let timeout = timeout.clone(); 93 | Rc::new(move || { 94 | let throttled_value = *throttled.borrow(); 95 | if !throttled_value { 96 | let callback_ref = callback_ref.current(); 97 | let callback = &mut *callback_ref.borrow_mut(); 98 | callback(); 99 | *throttled.borrow_mut() = true; 100 | timeout.reset(); 101 | } 102 | }) 103 | }; 104 | 105 | let cancel = { 106 | Rc::new(move || { 107 | timeout.cancel(); 108 | *throttled.borrow_mut() = false; 109 | }) 110 | }; 111 | 112 | UseThrottleHandle { run, cancel } 113 | } 114 | -------------------------------------------------------------------------------- /crates/yew-hooks/src/hooks/use_throttle_effect.rs: -------------------------------------------------------------------------------- 1 | use yew::prelude::*; 2 | 3 | use super::{use_throttle, use_unmount}; 4 | 5 | /// A hook that throttles calling effect callback, it is only called once every `millis`. 6 | /// 7 | /// # Example 8 | /// 9 | /// ```rust 10 | /// # use yew::prelude::*; 11 | /// # 12 | /// use yew_hooks::prelude::*; 13 | /// 14 | /// #[function_component(ThrottleEffect)] 15 | /// fn throttle_effect() -> Html { 16 | /// let state = use_state(|| 0); 17 | /// let update = use_update(); 18 | /// 19 | /// { 20 | /// let state = state.clone(); 21 | /// use_throttle_effect( 22 | /// move || { 23 | /// state.set(*state + 1); 24 | /// }, 25 | /// 2000, 26 | /// ) 27 | /// }; 28 | /// 29 | /// let onclick = { Callback::from(move |_| update()) }; 30 | /// 31 | /// html! { 32 | /// <> 33 | /// 34 | /// { "State: " } {*state} 35 | /// 36 | /// } 37 | /// } 38 | /// ``` 39 | #[hook] 40 | pub fn use_throttle_effect(callback: Callback, millis: u32) 41 | where 42 | Callback: FnMut() + 'static, 43 | { 44 | let throttle = use_throttle(callback, millis); 45 | 46 | { 47 | let throttle = throttle.clone(); 48 | use_effect(move || { 49 | throttle.run(); 50 | 51 | || () 52 | }); 53 | } 54 | 55 | use_unmount(move || { 56 | throttle.cancel(); 57 | }); 58 | } 59 | 60 | /// This hook is similar to [`use_throttle_effect`] but it accepts dependencies. 61 | /// 62 | /// Whenever the dependencies are changed, the throttle effect is run again. 63 | /// To detect changes, dependencies must implement `PartialEq`. 64 | #[hook] 65 | pub fn use_throttle_effect_with_deps( 66 | callback: Callback, 67 | millis: u32, 68 | deps: Dependents, 69 | ) where 70 | Callback: FnMut() + 'static, 71 | Dependents: PartialEq + 'static, 72 | { 73 | let throttle = use_throttle(callback, millis); 74 | 75 | { 76 | let throttle = throttle.clone(); 77 | use_effect_with(deps, move |_| { 78 | throttle.run(); 79 | 80 | || () 81 | }); 82 | } 83 | 84 | use_unmount(move || { 85 | throttle.cancel(); 86 | }); 87 | } 88 | -------------------------------------------------------------------------------- /crates/yew-hooks/src/hooks/use_throttle_state.rs: -------------------------------------------------------------------------------- 1 | use std::ops::Deref; 2 | use std::rc::Rc; 3 | 4 | use yew::prelude::*; 5 | 6 | use super::use_throttle; 7 | 8 | /// State handle for the [`use_throttle_state`] hook. 9 | pub struct UseThrottleStateHandle { 10 | inner: UseStateHandle, 11 | set: Rc, 12 | } 13 | 14 | impl UseThrottleStateHandle { 15 | // Set the value. 16 | pub fn set(&self, value: T) { 17 | (self.set)(value); 18 | } 19 | } 20 | 21 | impl Deref for UseThrottleStateHandle { 22 | type Target = T; 23 | 24 | fn deref(&self) -> &Self::Target { 25 | &self.inner 26 | } 27 | } 28 | 29 | impl Clone for UseThrottleStateHandle { 30 | fn clone(&self) -> Self { 31 | Self { 32 | inner: self.inner.clone(), 33 | set: self.set.clone(), 34 | } 35 | } 36 | } 37 | 38 | impl PartialEq for UseThrottleStateHandle 39 | where 40 | T: PartialEq, 41 | { 42 | fn eq(&self, other: &Self) -> bool { 43 | *self.inner == *other.inner 44 | } 45 | } 46 | 47 | /// A hook that throttles updating state, the state is only updated once every `millis`. 48 | /// 49 | /// # Example 50 | /// 51 | /// ```rust 52 | /// # use yew::prelude::*; 53 | /// # 54 | /// use yew_hooks::prelude::*; 55 | /// 56 | /// #[function_component(ThrottleState)] 57 | /// fn throttle_state() -> Html { 58 | /// let state = use_throttle_state(|| 0, 2000); 59 | /// 60 | /// let onclick = { 61 | /// let state = state.clone(); 62 | /// Callback::from(move |_| state.set(*state + 1)) 63 | /// }; 64 | /// 65 | /// html! { 66 | /// <> 67 | /// 68 | /// { "State: " } {*state} 69 | /// 70 | /// } 71 | /// } 72 | /// ``` 73 | #[hook] 74 | pub fn use_throttle_state(init_fn: F, millis: u32) -> UseThrottleStateHandle 75 | where 76 | T: 'static, 77 | F: FnOnce() -> T, 78 | { 79 | let value = use_mut_ref(|| None); 80 | let inner = use_state(init_fn); 81 | let throttle = { 82 | let value = value.clone(); 83 | let inner = inner.clone(); 84 | use_throttle( 85 | move || { 86 | let value = (*value.borrow_mut()).take(); 87 | if let Some(value) = value { 88 | inner.set(value); 89 | } 90 | }, 91 | millis, 92 | ) 93 | }; 94 | 95 | let set = { 96 | Rc::new(move |new_value: T| { 97 | *value.borrow_mut() = Some(new_value); 98 | throttle.run(); 99 | }) 100 | }; 101 | 102 | UseThrottleStateHandle { inner, set } 103 | } 104 | -------------------------------------------------------------------------------- /crates/yew-hooks/src/hooks/use_timeout.rs: -------------------------------------------------------------------------------- 1 | use std::rc::Rc; 2 | 3 | use gloo::timers::callback::Timeout; 4 | use yew::prelude::*; 5 | 6 | use super::{use_mut_latest, use_unmount}; 7 | 8 | /// State handle for the [`use_timeout`] hook. 9 | pub struct UseTimeoutHandle { 10 | reset: Rc, 11 | cancel: Rc, 12 | } 13 | 14 | impl UseTimeoutHandle { 15 | /// Reset the timeout. 16 | pub fn reset(&self) { 17 | (self.reset)(); 18 | } 19 | 20 | /// Cancel the timeout. 21 | pub fn cancel(&self) { 22 | (self.cancel)(); 23 | } 24 | } 25 | 26 | impl Clone for UseTimeoutHandle { 27 | fn clone(&self) -> Self { 28 | Self { 29 | reset: self.reset.clone(), 30 | cancel: self.cancel.clone(), 31 | } 32 | } 33 | } 34 | 35 | /// A hook that schedules a timeout to invoke `callback` in `millis` milliseconds from now. 36 | /// The timeout will be cancelled if `millis` is set to 0 or `cancel()` is called. 37 | /// 38 | /// # Example 39 | /// 40 | /// ```rust 41 | /// # use yew::prelude::*; 42 | /// # 43 | /// use yew_hooks::prelude::*; 44 | /// 45 | /// #[function_component(Timeout)] 46 | /// fn timeout() -> Html { 47 | /// let state = use_state(|| 0); 48 | /// 49 | /// let timeout = { 50 | /// let state = state.clone(); 51 | /// use_timeout(move || { 52 | /// state.set(*state + 1); 53 | /// }, 2000) 54 | /// }; 55 | /// 56 | /// let onreset = { 57 | /// let timeout = timeout.clone(); 58 | /// Callback::from(move |_| timeout.reset()) 59 | /// }; 60 | /// 61 | /// let oncancel = { 62 | /// let timeout = timeout.clone(); 63 | /// Callback::from(move |_| timeout.cancel()) 64 | /// }; 65 | /// 66 | /// html! { 67 | /// <> 68 | /// 69 | /// 70 | /// { *state } 71 | /// 72 | /// } 73 | /// } 74 | /// ``` 75 | #[hook] 76 | pub fn use_timeout(callback: Callback, millis: u32) -> UseTimeoutHandle 77 | where 78 | Callback: FnOnce() + 'static, 79 | { 80 | let callback_ref = use_mut_latest(Some(callback)); 81 | let timeout_ref = use_mut_ref(|| None); 82 | 83 | let reset = { 84 | let timeout_ref = timeout_ref.clone(); 85 | Rc::new(move || { 86 | let timeout_ref = timeout_ref.clone(); 87 | let callback_ref = callback_ref.clone(); 88 | if millis > 0 { 89 | *timeout_ref.borrow_mut() = Some(Timeout::new(millis, move || { 90 | let callback_ref = callback_ref.current(); 91 | let callback = (*callback_ref.borrow_mut()).take(); 92 | if let Some(callback) = callback { 93 | callback(); 94 | } 95 | })); 96 | } else { 97 | *timeout_ref.borrow_mut() = None; 98 | } 99 | }) 100 | }; 101 | 102 | let cancel = { 103 | let timeout_ref = timeout_ref.clone(); 104 | Rc::new(move || { 105 | *timeout_ref.borrow_mut() = None; 106 | }) 107 | }; 108 | 109 | { 110 | let reset = reset.clone(); 111 | let timeout_ref = timeout_ref.clone(); 112 | use_effect_with(millis, move |_| { 113 | reset(); 114 | 115 | move || *timeout_ref.borrow_mut() = None 116 | }); 117 | } 118 | 119 | use_unmount(move || { 120 | *timeout_ref.borrow_mut() = None; 121 | }); 122 | 123 | UseTimeoutHandle { reset, cancel } 124 | } 125 | -------------------------------------------------------------------------------- /crates/yew-hooks/src/hooks/use_title.rs: -------------------------------------------------------------------------------- 1 | use yew::prelude::*; 2 | 3 | use super::use_unmount; 4 | 5 | /// A side-effect hook that sets title of the page and restore previous title when unmount. 6 | /// 7 | /// # Example 8 | /// 9 | /// ```rust 10 | /// # use yew::prelude::*; 11 | /// # 12 | /// use yew_hooks::prelude::*; 13 | /// 14 | /// #[function_component(Title)] 15 | /// fn title() -> Html { 16 | /// use_title("This is an awesome title".to_string()); 17 | /// 18 | /// html! { 19 | /// <> 20 | /// 21 | /// } 22 | /// } 23 | /// ``` 24 | #[hook] 25 | pub fn use_title(title: String) { 26 | let pre_title = use_memo((), |_| gloo::utils::document().title()); 27 | 28 | if gloo::utils::document().title() != title { 29 | gloo::utils::document().set_title(&title); 30 | } 31 | 32 | use_unmount(move || { 33 | gloo::utils::document().set_title(&pre_title); 34 | }); 35 | } 36 | -------------------------------------------------------------------------------- /crates/yew-hooks/src/hooks/use_unmount.rs: -------------------------------------------------------------------------------- 1 | use yew::prelude::*; 2 | 3 | use super::{use_effect_once, use_mut_latest}; 4 | 5 | /// A lifecycle hook that calls a function when the component will unmount. 6 | /// 7 | /// # Example 8 | /// 9 | /// ```rust 10 | /// # use yew::prelude::*; 11 | /// # use log::debug; 12 | /// # 13 | /// use yew_hooks::prelude::*; 14 | /// 15 | /// #[function_component(Unmount)] 16 | /// fn unmount() -> Html { 17 | /// use_unmount(|| { 18 | /// debug!("Running clean-up of effect on unmount"); 19 | /// }); 20 | /// 21 | /// html! { 22 | /// <> 23 | /// 24 | /// } 25 | /// } 26 | /// ``` 27 | #[hook] 28 | pub fn use_unmount(callback: Callback) 29 | where 30 | Callback: FnOnce() + 'static, 31 | { 32 | let callback_ref = use_mut_latest(Some(callback)); 33 | 34 | use_effect_once(move || { 35 | move || { 36 | let callback_ref = callback_ref.current(); 37 | let callback = (*callback_ref.borrow_mut()).take(); 38 | if let Some(callback) = callback { 39 | callback(); 40 | } 41 | } 42 | }); 43 | } 44 | -------------------------------------------------------------------------------- /crates/yew-hooks/src/hooks/use_update.rs: -------------------------------------------------------------------------------- 1 | use std::rc::Rc; 2 | 3 | use yew::prelude::*; 4 | 5 | /// A hook returns a function that forces component to re-render when called. 6 | /// 7 | /// # Example 8 | /// 9 | /// ```rust 10 | /// # use yew::prelude::*; 11 | /// # 12 | /// use yew_hooks::prelude::*; 13 | /// 14 | /// #[function_component(Update)] 15 | /// fn update() -> Html { 16 | /// let update = use_update(); 17 | /// 18 | /// let onclick = Callback::from(move |_| { 19 | /// update(); 20 | /// }); 21 | /// 22 | /// html! { 23 | /// <> 24 | /// 25 | /// 26 | /// } 27 | /// } 28 | /// ``` 29 | #[hook] 30 | pub fn use_update() -> Rc { 31 | let state = use_state(|| 0); 32 | 33 | Rc::new(move || { 34 | state.set((*state + 1) % 1_000_000); 35 | }) 36 | } 37 | -------------------------------------------------------------------------------- /crates/yew-hooks/src/hooks/use_visible.rs: -------------------------------------------------------------------------------- 1 | use wasm_bindgen::{closure::Closure, JsCast}; 2 | use web_sys::{IntersectionObserver, IntersectionObserverEntry}; 3 | use yew::prelude::*; 4 | 5 | use super::use_effect_once; 6 | 7 | /// Check if an element is visible. Internally, it uses an [`IntersectionObserver`] to receive 8 | /// notifications from the browser whenever the visibility state of the node changes. 9 | /// 10 | /// Setting the sticky bit makes this hook disconnect the observer once the element is visible, and 11 | /// keep the visibility set to `true`, even when it becomes invisible. This is often desired 12 | /// for lazy-loading components. 13 | /// 14 | /// # Example 15 | /// 16 | /// ```rust 17 | /// # use yew::prelude::*; 18 | /// # 19 | /// use yew_hooks::prelude::*; 20 | /// 21 | /// #[function_component] 22 | /// fn MyComponent() -> Html { 23 | /// let node = use_node_ref(); 24 | /// let visible = use_visible(node.clone(), false); 25 | /// 26 | /// html! { 27 | ///
28 | /// if visible { 29 | ///

{"I'm visible!"}

30 | /// } else { 31 | ///

{"I'm invisible!"}

32 | /// } 33 | ///
34 | /// } 35 | /// } 36 | /// ``` 37 | #[hook] 38 | pub fn use_visible(node: NodeRef, sticky: bool) -> bool { 39 | // code adapted from: 40 | // https://stackoverflow.com/questions/1462138/event-listener-for-when-element-becomes-visible 41 | let visible = use_state_eq(|| false); 42 | let visible_clone = visible.clone(); 43 | 44 | use_effect_once(move || { 45 | let closure = Closure::, IntersectionObserver)>::new( 46 | move |entries: Vec, observer: IntersectionObserver| { 47 | // determine if any part of this node is visible. 48 | let visible = entries.iter().any(|entry| entry.intersection_ratio() > 0.0); 49 | 50 | // if the visibility changed, update the state. 51 | visible_clone.set(visible); 52 | 53 | // if this is sticky and it is currently visible, disconnect the observer. 54 | if visible && sticky { 55 | observer.disconnect(); 56 | } 57 | }, 58 | ) 59 | .into_js_value(); 60 | let observer = IntersectionObserver::new(closure.dyn_ref().unwrap()).unwrap(); 61 | if let Some(node) = node.get() { 62 | observer.observe(node.dyn_ref().unwrap()); 63 | } 64 | move || observer.disconnect() 65 | }); 66 | 67 | *visible 68 | } 69 | -------------------------------------------------------------------------------- /crates/yew-hooks/src/hooks/use_window_scroll.rs: -------------------------------------------------------------------------------- 1 | use gloo::utils::window; 2 | use yew::prelude::*; 3 | 4 | use super::{use_event_with_window, use_mount, use_raf_state}; 5 | 6 | /// A sensor hook that tracks Window scroll position. 7 | /// 8 | /// # Example 9 | /// 10 | /// ```rust 11 | /// # use yew::prelude::*; 12 | /// # 13 | /// use yew_hooks::prelude::*; 14 | /// 15 | /// #[function_component(UseWindowScroll)] 16 | /// fn window_scroll() -> Html { 17 | /// let state = use_window_scroll(); 18 | /// 19 | /// html! { 20 | /// <> 21 | /// { " X: " } 22 | /// { state.0 } 23 | /// { " Y: " } 24 | /// { state.1 } 25 | /// 26 | /// } 27 | /// } 28 | /// ``` 29 | #[hook] 30 | pub fn use_window_scroll() -> (f64, f64) { 31 | let state = use_raf_state(|| { 32 | ( 33 | window().page_x_offset().unwrap(), 34 | window().page_y_offset().unwrap(), 35 | ) 36 | }); 37 | 38 | { 39 | let state = state.clone(); 40 | use_event_with_window("scroll", move |_: Event| { 41 | state.set(( 42 | window().page_x_offset().unwrap(), 43 | window().page_y_offset().unwrap(), 44 | )); 45 | }); 46 | } 47 | 48 | { 49 | let state = state.clone(); 50 | use_mount(move || { 51 | state.set(( 52 | window().page_x_offset().unwrap(), 53 | window().page_y_offset().unwrap(), 54 | )); 55 | }); 56 | } 57 | 58 | *state 59 | } 60 | -------------------------------------------------------------------------------- /crates/yew-hooks/src/hooks/use_window_size.rs: -------------------------------------------------------------------------------- 1 | use gloo::utils::window; 2 | use yew::prelude::*; 3 | 4 | use super::{use_event_with_window, use_mount, use_raf_state}; 5 | 6 | /// A sensor hook that tracks dimensions of the browser window. 7 | /// 8 | /// # Example 9 | /// 10 | /// ```rust 11 | /// # use yew::prelude::*; 12 | /// # 13 | /// use yew_hooks::prelude::*; 14 | /// 15 | /// #[function_component(UseWindowSize)] 16 | /// fn window_size() -> Html { 17 | /// let state = use_window_size(); 18 | /// 19 | /// html! { 20 | /// <> 21 | /// { " Width: " } 22 | /// { state.0 } 23 | /// { " Height: " } 24 | /// { state.1 } 25 | /// 26 | /// } 27 | /// } 28 | /// ``` 29 | #[hook] 30 | pub fn use_window_size() -> (f64, f64) { 31 | let state = use_raf_state(|| { 32 | ( 33 | window().inner_width().unwrap().as_f64().unwrap(), 34 | window().inner_height().unwrap().as_f64().unwrap(), 35 | ) 36 | }); 37 | 38 | { 39 | let state = state.clone(); 40 | use_event_with_window("resize", move |_: Event| { 41 | state.set(( 42 | window().inner_width().unwrap().as_f64().unwrap(), 43 | window().inner_height().unwrap().as_f64().unwrap(), 44 | )); 45 | }); 46 | } 47 | 48 | { 49 | let state = state.clone(); 50 | use_mount(move || { 51 | state.set(( 52 | window().inner_width().unwrap().as_f64().unwrap(), 53 | window().inner_height().unwrap().as_f64().unwrap(), 54 | )); 55 | }); 56 | } 57 | 58 | *state 59 | } 60 | -------------------------------------------------------------------------------- /crates/yew-hooks/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! # Yew Hooks 2 | //! 3 | //! Hooks for [Yew](https://github.com/yewstack/yew), inspired by 4 | //! [streamich/react-use](https://github.com/streamich/react-use) and 5 | //! [alibaba/hooks](https://github.com/alibaba/hooks). 6 | //! 7 | //! ## Examples 8 | //! 9 | //! ```rust 10 | //! use yew::prelude::*; 11 | //! 12 | //! use yew_hooks::use_counter; 13 | //! 14 | //! #[function_component(Counter)] 15 | //! fn counter() -> Html { 16 | //! let counter = use_counter(0); 17 | //! 18 | //! let onincrease = { 19 | //! let counter = counter.clone(); 20 | //! Callback::from(move |_| counter.increase()) 21 | //! }; 22 | //! let ondecrease = { 23 | //! let counter = counter.clone(); 24 | //! Callback::from(move |_| counter.decrease()) 25 | //! }; 26 | //! let onincreaseby = { 27 | //! let counter = counter.clone(); 28 | //! Callback::from(move |_| counter.increase_by(10)) 29 | //! }; 30 | //! let ondecreaseby = { 31 | //! let counter = counter.clone(); 32 | //! Callback::from(move |_| counter.decrease_by(10)) 33 | //! }; 34 | //! let onset = { 35 | //! let counter = counter.clone(); 36 | //! Callback::from(move |_| counter.set(100)) 37 | //! }; 38 | //! let onreset = { 39 | //! let counter = counter.clone(); 40 | //! Callback::from(move |_| counter.reset()) 41 | //! }; 42 | //! 43 | //! html! { 44 | //!
45 | //! 46 | //! 47 | //! 48 | //! 49 | //! 50 | //! 51 | //!

52 | //! { "Current value: " } 53 | //! { *counter } 54 | //!

55 | //!
56 | //! } 57 | //! } 58 | //! ``` 59 | //! 60 | //! ## Demo 61 | //! 62 | //! [Check out a live demo](https://jetli.github.io/yew-hooks/) 63 | 64 | mod hooks; 65 | pub(crate) mod web_sys_ext; 66 | 67 | pub use hooks::*; 68 | 69 | pub mod prelude { 70 | pub use crate::hooks::*; 71 | } 72 | -------------------------------------------------------------------------------- /crates/yew-hooks/tests/common/mod.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | 3 | pub fn obtain_result() -> String { 4 | gloo::utils::document() 5 | .get_element_by_id("result") 6 | .expect("No result found. Most likely, the application crashed and burned") 7 | .inner_html() 8 | } 9 | 10 | pub fn obtain_result_by_id(id: &str) -> String { 11 | gloo::utils::document() 12 | .get_element_by_id(id) 13 | .expect("No result found. Most likely, the application crashed and burned") 14 | .inner_html() 15 | } 16 | -------------------------------------------------------------------------------- /crates/yew-hooks/tests/use_counter.rs: -------------------------------------------------------------------------------- 1 | use std::time::Duration; 2 | use wasm_bindgen_test::*; 3 | use yew::platform::time::sleep; 4 | use yew::prelude::*; 5 | 6 | mod common; 7 | 8 | use common::obtain_result; 9 | 10 | wasm_bindgen_test_configure!(run_in_browser); 11 | 12 | use yew_hooks::use_counter; 13 | 14 | #[wasm_bindgen_test] 15 | async fn use_counter_works() { 16 | #[function_component] 17 | fn TestComponent() -> Html { 18 | let counter = use_counter(0); 19 | if *counter < 5 { 20 | counter.increase(); 21 | } 22 | html! { 23 |
24 | {"Test Output: "} 25 |
{*counter}
26 | {"\n"} 27 |
28 | } 29 | } 30 | 31 | yew::Renderer::::with_root( 32 | gloo::utils::document().get_element_by_id("output").unwrap(), 33 | ) 34 | .render(); 35 | sleep(Duration::ZERO).await; 36 | let result = obtain_result(); 37 | assert_eq!(result.as_str(), "5"); 38 | } 39 | -------------------------------------------------------------------------------- /examples/yew-app/.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /target/ 4 | 5 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 6 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 7 | Cargo.lock 8 | 9 | # These are backup files generated by rustfmt 10 | **/*.rs.bk 11 | 12 | 13 | # JS 14 | node_modules 15 | /dist 16 | /pkg 17 | /wasm-pack.log 18 | .cache -------------------------------------------------------------------------------- /examples/yew-app/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["You "] 3 | categories = ["wasm"] 4 | description = "My awesome Yew app." 5 | edition = "2021" 6 | license = "Apache-2.0/MIT" 7 | name = "yew-app" 8 | readme = "./README.md" 9 | repository = "https://github.com/jetli/create-yew-app.git" 10 | version = "0.1.0" 11 | 12 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 13 | 14 | [dependencies] 15 | log = "0.4" 16 | gloo = "0.11" 17 | js-sys = "0.3" 18 | serde = "1" 19 | reqwest = { version = "0.12.4", features = ["json"] } 20 | yew = { version = "0.21.0", features = ["csr"] } 21 | yew-router = { version = "0.18.0" } 22 | yew-hooks = { path = "../../crates/yew-hooks" } 23 | wasm-bindgen = "0.2" 24 | wasm-logger = "0.2.0" 25 | wee_alloc = "0.4.5" 26 | 27 | [dependencies.web-sys] 28 | version = "0.3" 29 | features = ["Window"] 30 | 31 | [dev-dependencies] 32 | wasm-bindgen-test = "0.3.14" 33 | 34 | [dev-dependencies.web-sys] 35 | version = "0.3" 36 | features = ["Document", "Element", "HtmlCollection"] 37 | -------------------------------------------------------------------------------- /examples/yew-app/LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2018 The Rust Project Developers 2 | 3 | Permission is hereby granted, free of charge, to any 4 | person obtaining a copy of this software and associated 5 | documentation files (the "Software"), to deal in the 6 | Software without restriction, including without 7 | limitation the rights to use, copy, modify, merge, 8 | publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software 10 | is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice 14 | shall be included in all copies or substantial portions 15 | of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /examples/yew-app/README.md: -------------------------------------------------------------------------------- 1 | # My Awesome Yew App 2 | 3 | This is the official base template for [Create Yew App](https://github.com/jetli/create-yew-app). 4 | 5 | ## How to run in debug mode 6 | 7 | ```sh 8 | # Builds the project and opens it in a new browser tab. Auto-reloads when the project changes. 9 | trunk serve 10 | ``` 11 | 12 | ## How to build in release mode 13 | 14 | ```sh 15 | # Builds the project and places it into the `dist` folder. 16 | trunk build 17 | ``` 18 | 19 | ## How to run unit tests 20 | 21 | ```sh 22 | # Runs tests 23 | wasm-pack test --headless --chrome 24 | ``` 25 | -------------------------------------------------------------------------------- /examples/yew-app/Trunk.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | target = "index.html" 3 | dist = "dist" 4 | -------------------------------------------------------------------------------- /examples/yew-app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Yew Hooks 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /examples/yew-app/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jetli/yew-hooks/ec4584ad771b425e557942b968339fed1d28347c/examples/yew-app/public/favicon.ico -------------------------------------------------------------------------------- /examples/yew-app/public/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /examples/yew-app/src/app/about.rs: -------------------------------------------------------------------------------- 1 | use yew::prelude::*; 2 | 3 | /// About page 4 | #[function_component(About)] 5 | pub fn about() -> Html { 6 | html! { 7 |
8 |
9 |

10 | 12 | { "Yew Hooks" } 13 | 14 | { ", hooks for Yew, inspired by streamich/react-use and alibaba/hooks." } 15 |

16 |

17 | { "Edit " } { "src/app/about.rs" } { " and save to reload." } 18 |

19 |
20 |
21 | } 22 | } 23 | 24 | #[cfg(test)] 25 | mod tests { 26 | use std::time::Duration; 27 | use wasm_bindgen_test::*; 28 | use yew::platform::time::sleep; 29 | 30 | wasm_bindgen_test_configure!(run_in_browser); 31 | 32 | use super::About; 33 | 34 | #[wasm_bindgen_test] 35 | async fn about_page_has_an_app_link() { 36 | yew::Renderer::::with_root( 37 | gloo::utils::document().get_element_by_id("output").unwrap(), 38 | ) 39 | .render(); 40 | 41 | sleep(Duration::ZERO).await; 42 | 43 | let app_links = gloo::utils::document().get_elements_by_tag_name("a"); 44 | 45 | assert_eq!(app_links.length(), 1); 46 | 47 | let link = app_links.item(0).expect("No app-link").inner_html(); 48 | assert_eq!(link, "Yew Hooks"); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /examples/yew-app/src/app/hooks/mod.rs: -------------------------------------------------------------------------------- 1 | mod use_async; 2 | mod use_before_unload; 3 | mod use_bool_toggle; 4 | mod use_click_away; 5 | mod use_clipboard; 6 | mod use_counter; 7 | mod use_debounce; 8 | mod use_debounce_effect; 9 | mod use_debounce_state; 10 | mod use_default; 11 | mod use_drag; 12 | mod use_drop; 13 | mod use_effect_once; 14 | mod use_effect_update; 15 | mod use_event; 16 | mod use_favicon; 17 | mod use_geolocation; 18 | mod use_hash; 19 | mod use_hovered; 20 | mod use_infinite_scroll; 21 | mod use_interval; 22 | mod use_is_first_mount; 23 | mod use_is_mounted; 24 | mod use_latest; 25 | mod use_list; 26 | mod use_local_storage; 27 | mod use_location; 28 | mod use_logger; 29 | mod use_map; 30 | mod use_measure; 31 | mod use_media; 32 | mod use_mount; 33 | mod use_mut_latest; 34 | mod use_previous; 35 | mod use_queue; 36 | mod use_raf; 37 | mod use_raf_state; 38 | mod use_renders_count; 39 | mod use_scroll; 40 | mod use_scrolling; 41 | mod use_search_param; 42 | mod use_session_storage; 43 | mod use_set; 44 | mod use_size; 45 | mod use_state_ptr_eq; 46 | mod use_swipe; 47 | mod use_throttle; 48 | mod use_throttle_effect; 49 | mod use_throttle_state; 50 | mod use_timeout; 51 | mod use_title; 52 | mod use_toggle; 53 | mod use_unmount; 54 | mod use_update; 55 | mod use_visible; 56 | mod use_websocket; 57 | mod use_window_scroll; 58 | mod use_window_size; 59 | 60 | pub use use_async::*; 61 | pub use use_before_unload::*; 62 | pub use use_bool_toggle::*; 63 | pub use use_click_away::*; 64 | pub use use_clipboard::*; 65 | pub use use_counter::*; 66 | pub use use_debounce::*; 67 | pub use use_debounce_effect::*; 68 | pub use use_debounce_state::*; 69 | pub use use_default::*; 70 | pub use use_drag::*; 71 | pub use use_drop::*; 72 | pub use use_effect_once::*; 73 | pub use use_effect_update::*; 74 | pub use use_event::*; 75 | pub use use_favicon::*; 76 | pub use use_geolocation::*; 77 | pub use use_hash::*; 78 | pub use use_hovered::*; 79 | pub use use_infinite_scroll::*; 80 | pub use use_interval::*; 81 | pub use use_is_first_mount::*; 82 | pub use use_is_mounted::*; 83 | pub use use_latest::*; 84 | pub use use_list::*; 85 | pub use use_local_storage::*; 86 | pub use use_location::*; 87 | pub use use_logger::*; 88 | pub use use_map::*; 89 | pub use use_measure::*; 90 | pub use use_media::*; 91 | pub use use_mount::*; 92 | pub use use_mut_latest::*; 93 | pub use use_previous::*; 94 | pub use use_queue::*; 95 | pub use use_raf::*; 96 | pub use use_raf_state::*; 97 | pub use use_renders_count::*; 98 | pub use use_scroll::*; 99 | pub use use_scrolling::*; 100 | pub use use_search_param::*; 101 | pub use use_session_storage::*; 102 | pub use use_set::*; 103 | pub use use_size::*; 104 | pub use use_state_ptr_eq::*; 105 | pub use use_swipe::*; 106 | pub use use_throttle::*; 107 | pub use use_throttle_effect::*; 108 | pub use use_throttle_state::*; 109 | pub use use_timeout::*; 110 | pub use use_title::*; 111 | pub use use_toggle::*; 112 | pub use use_unmount::*; 113 | pub use use_update::*; 114 | pub use use_visible::*; 115 | pub use use_websocket::*; 116 | pub use use_window_scroll::*; 117 | pub use use_window_size::*; 118 | -------------------------------------------------------------------------------- /examples/yew-app/src/app/hooks/use_before_unload.rs: -------------------------------------------------------------------------------- 1 | use yew::prelude::*; 2 | use yew_hooks::prelude::*; 3 | 4 | use crate::components::ui::button::Button; 5 | 6 | /// `use_before_unload` demo 7 | #[function_component] 8 | pub fn UseBeforeUnload() -> Html { 9 | let dirty = use_bool_toggle(false); 10 | use_before_unload( 11 | *dirty, 12 | "You have unsaved changes, are you sure?".to_string(), 13 | ); 14 | 15 | let onclick = { 16 | let dirty = dirty.clone(); 17 | Callback::from(move |_| dirty.toggle()) 18 | }; 19 | 20 | html! { 21 |
22 |
23 |
24 | 25 |

26 | {if *dirty { "Try to reload or close tab." } else { "" }} 27 |

28 |
29 |
30 |
31 | } 32 | } 33 | -------------------------------------------------------------------------------- /examples/yew-app/src/app/hooks/use_bool_toggle.rs: -------------------------------------------------------------------------------- 1 | use yew::prelude::*; 2 | use yew_hooks::prelude::*; 3 | 4 | use crate::components::ui::button::Button; 5 | 6 | /// `use_bool_toggle` demo 7 | #[function_component] 8 | pub fn UseBoolToggle() -> Html { 9 | let toggle = use_bool_toggle(true); 10 | 11 | let onclick = { 12 | let toggle = toggle.clone(); 13 | Callback::from(move |_| toggle.toggle()) 14 | }; 15 | let onreset = { 16 | let toggle = toggle.clone(); 17 | Callback::from(move |_| toggle.reset()) 18 | }; 19 | let onset = { 20 | let toggle = toggle.clone(); 21 | Callback::from(move |_| toggle.set(false)) 22 | }; 23 | 24 | html! { 25 |
26 |
27 |
28 | 29 | 30 | 31 |

32 | { "Current value: " } 33 | { *toggle } 34 |

35 |
36 |
37 |
38 | } 39 | } 40 | -------------------------------------------------------------------------------- /examples/yew-app/src/app/hooks/use_click_away.rs: -------------------------------------------------------------------------------- 1 | use gloo::dialogs::alert; 2 | use yew::prelude::*; 3 | use yew_hooks::prelude::*; 4 | 5 | /// `use_click_away` demo 6 | #[function_component] 7 | pub fn UseClickAway() -> Html { 8 | let node = use_node_ref(); 9 | use_click_away(node.clone(), move |_: Event| { 10 | alert("Clicked outside!"); 11 | }); 12 | 13 | html! { 14 |
15 |
16 |
17 |

18 | { "Try to click outside of this area." } 19 |

20 |
21 |
22 |
23 | } 24 | } 25 | -------------------------------------------------------------------------------- /examples/yew-app/src/app/hooks/use_clipboard.rs: -------------------------------------------------------------------------------- 1 | use yew::prelude::*; 2 | use yew_hooks::prelude::*; 3 | 4 | use crate::components::ui::button::Button; 5 | 6 | /// `use_clipboard` demo 7 | #[function_component] 8 | pub fn UseClipboard() -> Html { 9 | let clipboard = use_clipboard(); 10 | let logo = use_async_with_options( 11 | async move { 12 | if let Ok(response) = reqwest::get( 13 | "https://raw.githubusercontent.com/yewstack/yew/master/website/static/img/logo.png", 14 | ) 15 | .await 16 | { 17 | (response.bytes().await).map_or(Err("Bytes error"), |bytes| Ok(bytes.to_vec())) 18 | } else { 19 | Err("Response err") 20 | } 21 | }, 22 | UseAsyncOptions::enable_auto(), 23 | ); 24 | 25 | let onclick = { 26 | let clipboard = clipboard.clone(); 27 | Callback::from(move |_| { 28 | clipboard.write_text("hello world!".to_owned()); 29 | }) 30 | }; 31 | 32 | let onclick_read_text = { 33 | let clipboard = clipboard.clone(); 34 | Callback::from(move |_| { 35 | clipboard.read_text(); 36 | }) 37 | }; 38 | 39 | let onclick_write_bytes = { 40 | let clipboard = clipboard.clone(); 41 | Callback::from(move |_| { 42 | if let Some(bytes) = &logo.data { 43 | clipboard.write(bytes.clone(), Some("image/png".to_owned())); 44 | } 45 | }) 46 | }; 47 | 48 | let onclick_read_bytes = { 49 | let clipboard = clipboard.clone(); 50 | Callback::from(move |_| { 51 | clipboard.read(); 52 | }) 53 | }; 54 | 55 | html! { 56 |
57 |
58 |
59 | 60 | 61 | 62 | 63 |

{ format!("Current text: {:?}", *clipboard.text) }

64 |

{ format!("Copied: {:?}", *clipboard.copied) }

65 |

{ format!("Is supported: {:?}", *clipboard.is_supported) }

66 |

{ format!("Current bytes: {:?}", *clipboard.bytes) }

67 |

{ format!("Current bytes mime type: {:?}", *clipboard.bytes_mime_type) }

68 |

69 |
70 |
71 |
72 | } 73 | } 74 | -------------------------------------------------------------------------------- /examples/yew-app/src/app/hooks/use_counter.rs: -------------------------------------------------------------------------------- 1 | use yew::prelude::*; 2 | use yew_hooks::prelude::*; 3 | 4 | use crate::components::ui::button::Button; 5 | 6 | /// `use_counter` demo 7 | #[function_component] 8 | pub fn UseCounter() -> Html { 9 | let counter = use_counter(0); 10 | 11 | let onincrease = { 12 | let counter = counter.clone(); 13 | Callback::from(move |_| counter.increase()) 14 | }; 15 | let ondecrease = { 16 | let counter = counter.clone(); 17 | Callback::from(move |_| counter.decrease()) 18 | }; 19 | let onincreaseby = { 20 | let counter = counter.clone(); 21 | Callback::from(move |_| counter.increase_by(10)) 22 | }; 23 | let ondecreaseby = { 24 | let counter = counter.clone(); 25 | Callback::from(move |_| counter.decrease_by(10)) 26 | }; 27 | let onset = { 28 | let counter = counter.clone(); 29 | Callback::from(move |_| counter.set(100)) 30 | }; 31 | let onreset = { 32 | let counter = counter.clone(); 33 | Callback::from(move |_| counter.reset()) 34 | }; 35 | 36 | html! { 37 |
38 |
39 |
40 | 41 | 42 | 43 | 44 | 45 | 46 |

47 | { "Current value: " } 48 | { *counter } 49 |

50 |
51 |
52 |
53 | } 54 | } 55 | -------------------------------------------------------------------------------- /examples/yew-app/src/app/hooks/use_debounce.rs: -------------------------------------------------------------------------------- 1 | use web_sys::HtmlInputElement; 2 | use yew::prelude::*; 3 | use yew_hooks::prelude::*; 4 | 5 | use crate::components::ui::button::Button; 6 | 7 | /// `use_debounce` demo 8 | #[function_component] 9 | pub fn UseDebounce() -> Html { 10 | let status = use_state(|| "Typing stopped".to_string()); 11 | let value = use_state(String::new); 12 | let debounced_value = use_state(String::new); 13 | 14 | let debounce = { 15 | let status = status.clone(); 16 | let value = value.clone(); 17 | let debounced_value = debounced_value.clone(); 18 | use_debounce( 19 | move || { 20 | debounced_value.set((*value).clone()); 21 | status.set("Typing stopped".to_string()); 22 | }, 23 | 2000, 24 | ) 25 | }; 26 | 27 | let oninput = { 28 | let status = status.clone(); 29 | let value = value.clone(); 30 | let debounce = debounce.clone(); 31 | Callback::from(move |e: InputEvent| { 32 | let input: HtmlInputElement = e.target_unchecked_into(); 33 | value.set(input.value()); 34 | status.set("Waiting for typing to stop...".to_string()); 35 | debounce.run(); 36 | }) 37 | }; 38 | 39 | let onclick = { Callback::from(move |_| debounce.cancel()) }; 40 | 41 | html! { 42 |
43 |
44 |
45 | 46 | 47 |

{&*status}

48 |

49 | { "Value: " } {&*value} 50 |

51 |

52 | { "Debounced value: " } {&*debounced_value} 53 |

54 |
55 |
56 |
57 | } 58 | } 59 | -------------------------------------------------------------------------------- /examples/yew-app/src/app/hooks/use_debounce_effect.rs: -------------------------------------------------------------------------------- 1 | use web_sys::HtmlInputElement; 2 | use yew::prelude::*; 3 | use yew_hooks::prelude::*; 4 | 5 | /// `use_debounce_effect` demo 6 | #[function_component] 7 | pub fn UseDebounceEffect() -> Html { 8 | let status = use_state(|| "Typing stopped".to_string()); 9 | let value = use_state(String::new); 10 | let debounced_value = use_state(String::new); 11 | 12 | { 13 | let status = status.clone(); 14 | let value = value.clone(); 15 | let debounced_value = debounced_value.clone(); 16 | use_debounce_effect( 17 | move || { 18 | // This will delay updating state. 19 | debounced_value.set((*value).clone()); 20 | status.set("Typing stopped".to_string()); 21 | }, 22 | 2000, 23 | ); 24 | } 25 | 26 | let oninput = { 27 | let status = status.clone(); 28 | let value = value.clone(); 29 | Callback::from(move |e: InputEvent| { 30 | let input: HtmlInputElement = e.target_unchecked_into(); 31 | // This will update state every time. 32 | value.set(input.value()); 33 | status.set("Waiting for typing to stop...".to_string()); 34 | }) 35 | }; 36 | 37 | html! { 38 |
39 |
40 |
41 | 42 |

{&*status}

43 |

44 | { "Value: " } {&*value} 45 |

46 |

47 | { "Debounced value: " } {&*debounced_value} 48 |

49 |
50 |
51 |
52 | } 53 | } 54 | -------------------------------------------------------------------------------- /examples/yew-app/src/app/hooks/use_debounce_state.rs: -------------------------------------------------------------------------------- 1 | use web_sys::HtmlInputElement; 2 | use yew::prelude::*; 3 | use yew_hooks::prelude::*; 4 | 5 | /// `use_debounce_state` demo 6 | #[function_component] 7 | pub fn UseDebounceState() -> Html { 8 | let value = use_state(String::new); 9 | let debounced_value = use_debounce_state(String::new, 2000); 10 | 11 | let oninput = { 12 | let value = value.clone(); 13 | let debounced_value = debounced_value.clone(); 14 | Callback::from(move |e: InputEvent| { 15 | let input: HtmlInputElement = e.target_unchecked_into(); 16 | // This will update state every time. 17 | value.set(input.value()); 18 | // This will delay updating state. 19 | debounced_value.set(input.value()); 20 | }) 21 | }; 22 | 23 | html! { 24 |
25 |
26 |
27 | 28 |

29 | { "Value: " } {&*value} 30 |

31 |

32 | { "Debounced value: " } {&*debounced_value} 33 |

34 |
35 |
36 |
37 | } 38 | } 39 | -------------------------------------------------------------------------------- /examples/yew-app/src/app/hooks/use_default.rs: -------------------------------------------------------------------------------- 1 | use yew::prelude::*; 2 | use yew_hooks::prelude::*; 3 | 4 | use crate::components::ui::button::Button; 5 | 6 | /// `use_default` demo 7 | #[function_component] 8 | pub fn UseDefault() -> Html { 9 | let state = use_default(|| None, "Hello(default)".to_string()); 10 | 11 | let onclick = { 12 | let state = state.clone(); 13 | Callback::from(move |_| { 14 | state.set(Some("World!".to_string())); 15 | }) 16 | }; 17 | 18 | let onclear = { 19 | let state = state.clone(); 20 | Callback::from(move |_| { 21 | state.set(None); 22 | }) 23 | }; 24 | 25 | html! { 26 |
27 |
28 |
29 | 30 | 31 |

32 | { "Current value: " } 33 | { &*state } 34 |

35 |
36 |
37 |
38 | } 39 | } 40 | -------------------------------------------------------------------------------- /examples/yew-app/src/app/hooks/use_drag.rs: -------------------------------------------------------------------------------- 1 | use yew::prelude::*; 2 | use yew_hooks::prelude::*; 3 | 4 | /// `use_drag` demo 5 | #[function_component] 6 | pub fn UseDragComponent() -> Html { 7 | let node = use_node_ref(); 8 | // Demo #1, simply without callback options. 9 | let state = use_drag(node.clone()); 10 | 11 | // Demo #2, use callback options. 12 | let _ = use_drag_with_options( 13 | node.clone(), 14 | UseDragOptions { 15 | ondragstart: Some(Box::new(move |e| { 16 | if let Some(data_transfer) = e.data_transfer() { 17 | // You can send some text via DataTransfer to the dropped target. 18 | let _ = data_transfer.set_data("text", "hello from draggable component"); 19 | } 20 | })), 21 | ..Default::default() 22 | }, 23 | ); 24 | 25 | html! { 26 |
30 |

31 | { " Dagging: " } 32 | { *state.dragging } 33 |

34 |

35 | { "Try to drag this area" } 36 |

37 |
38 | } 39 | } 40 | 41 | #[function_component] 42 | pub fn UseDrag() -> Html { 43 | let node = use_node_ref(); 44 | let state = use_drop(node.clone()); 45 | 46 | html! { 47 |
48 |
49 | 50 | 51 |
55 |

{ " Text: " }

56 | { 57 | (*state.text).as_ref().map_or_else(|| html! {}, 58 | |text| html! {

{ text }

}) 59 | } 60 |

61 | { "Try to drop something to this area" } 62 |

63 |
64 |
65 |
66 | } 67 | } 68 | -------------------------------------------------------------------------------- /examples/yew-app/src/app/hooks/use_drop.rs: -------------------------------------------------------------------------------- 1 | use yew::prelude::*; 2 | use yew_hooks::prelude::*; 3 | 4 | /// `use_drop` demo 5 | #[function_component] 6 | pub fn UseDrop() -> Html { 7 | let node = use_node_ref(); 8 | // Demo #1, simply without callback options. 9 | let state = use_drop(node.clone()); 10 | 11 | // Demo #2, use callback options. 12 | let _ = use_drop_with_options( 13 | node.clone(), 14 | UseDropOptions { 15 | onfiles: Some(Box::new(move |_files, _data_transfer| { 16 | // Process files or data_transfer 17 | })), 18 | ..Default::default() 19 | }, 20 | ); 21 | 22 | html! { 23 |
24 |
25 |
29 |

{ " Files: " }

30 | { 31 | (*state.files).as_ref().map_or_else( 32 | || html! {}, 33 | |files| { 34 | html! {for files.iter().map(|file| { 35 | html! {

{ file.name() }

} 36 | })} 37 | }, 38 | ) 39 | } 40 |

{ " Text: " }

41 | { 42 | (*state.text) 43 | .as_ref() 44 | .map_or_else(|| html! {}, |text| html! {

{ text }

}) 45 | } 46 |

{ " Uri: " }

47 | { 48 | (*state.uri) 49 | .as_ref() 50 | .map_or_else(|| html! {}, |uri| html! {

{ uri }

}) 51 | } 52 |

53 | { "Try to drag & drop or copy & paste something here, e.g. files, links or text" } 54 |

55 |
56 |
57 |
58 | } 59 | } 60 | -------------------------------------------------------------------------------- /examples/yew-app/src/app/hooks/use_effect_once.rs: -------------------------------------------------------------------------------- 1 | use gloo::dialogs::alert; 2 | use yew::prelude::*; 3 | use yew_hooks::prelude::*; 4 | 5 | use crate::components::ui::button::Button; 6 | 7 | /// `use_effect_once` demo 8 | #[function_component] 9 | fn MyComponent() -> Html { 10 | use_effect_once(|| { 11 | alert("Running effect once on mount"); 12 | 13 | || alert("Running clean-up of effect on unmount") 14 | }); 15 | 16 | html! { 17 | <>{ "My Component" } 18 | } 19 | } 20 | 21 | #[function_component] 22 | pub fn UseEffectOnce() -> Html { 23 | let toggle = use_toggle("Mount", "Unmount"); 24 | 25 | let onclick = { 26 | let toggle = toggle.clone(); 27 | Callback::from(move |_| toggle.toggle()) 28 | }; 29 | 30 | html! { 31 |
32 |
33 |
34 | 35 |

36 | { 37 | if *toggle == "Unmount" { 38 | html! { } 39 | } else { 40 | html! {} 41 | } 42 | } 43 |

44 |
45 |
46 |
47 | } 48 | } 49 | -------------------------------------------------------------------------------- /examples/yew-app/src/app/hooks/use_effect_update.rs: -------------------------------------------------------------------------------- 1 | use yew::prelude::*; 2 | use yew_hooks::prelude::*; 3 | 4 | use crate::components::ui::button::Button; 5 | 6 | /// `use_effect_update` demo 7 | #[function_component] 8 | pub fn UseEffectUpdate() -> Html { 9 | let count = use_state(|| 0); 10 | let count_effect = use_state(|| 0); 11 | let count_effect_update = use_state(|| 0); 12 | 13 | { 14 | let count_effect = count_effect.clone(); 15 | use_effect_with(count.clone(), move |_| { 16 | count_effect.set(*count_effect + 1); 17 | || () 18 | }); 19 | } 20 | 21 | { 22 | let count_effect_update = count_effect_update.clone(); 23 | let count = count.clone(); 24 | // Count for use_effect_update_with_deps is less than use_effect_with by 1. 25 | use_effect_update_with_deps( 26 | move |_| { 27 | count_effect_update.set(*count_effect_update + 1); 28 | || () 29 | }, 30 | count, 31 | ); 32 | } 33 | 34 | let onclick = Callback::from(move |_| { 35 | count.set(*count + 1); 36 | }); 37 | 38 | html! { 39 |
40 |
41 |
42 | 43 |

44 | { " Count effect: " } 45 | { *count_effect } 46 | { " Count effect update: " } 47 | { *count_effect_update } 48 |

49 |
50 |
51 |
52 | } 53 | } 54 | -------------------------------------------------------------------------------- /examples/yew-app/src/app/hooks/use_event.rs: -------------------------------------------------------------------------------- 1 | use gloo::dialogs::alert; 2 | use yew::prelude::*; 3 | use yew_hooks::prelude::*; 4 | 5 | use crate::components::ui::button::Button; 6 | 7 | /// `use_event` demo 8 | #[function_component] 9 | pub fn UseEvent() -> Html { 10 | let button = use_node_ref(); 11 | 12 | use_event(button.clone(), "click", move |_: MouseEvent| { 13 | alert("Clicked!"); 14 | }); 15 | 16 | use_event_with_window("keypress", move |e: KeyboardEvent| { 17 | alert(format!("{} is pressed!", e.key()).as_str()); 18 | }); 19 | 20 | html! { 21 |
22 |
23 |
24 | 25 |

26 | { "or Press any key on your awesome keyboard. " } 27 |

28 |
29 |
30 |
31 | } 32 | } 33 | -------------------------------------------------------------------------------- /examples/yew-app/src/app/hooks/use_favicon.rs: -------------------------------------------------------------------------------- 1 | use yew::prelude::*; 2 | use yew_hooks::prelude::*; 3 | 4 | /// `use_favicon` demo 5 | #[function_component] 6 | pub fn UseFavicon() -> Html { 7 | use_favicon("https://crates.io/favicon.ico".to_string()); 8 | 9 | html! { 10 | <>{ "View the favicon of this page" } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /examples/yew-app/src/app/hooks/use_geolocation.rs: -------------------------------------------------------------------------------- 1 | use yew::prelude::*; 2 | use yew_hooks::prelude::*; 3 | 4 | /// `use_geolocation` demo 5 | #[function_component] 6 | pub fn UseGeolocation() -> Html { 7 | let state = use_geolocation(); 8 | 9 | html! { 10 |
11 |
12 |
13 |

14 | { " loading: " } 15 | { state.loading } 16 | { " latitude: " } 17 | { state.latitude } 18 | { " longitude: " } 19 | { state.longitude } 20 | { " altitude: " } 21 | { state.altitude.unwrap_or_default() } 22 | { " altitude_accuracy: " } 23 | { state.altitude_accuracy.unwrap_or_default() } 24 | { " heading: " } 25 | { state.heading.unwrap_or_default() } 26 | { " speed: " } 27 | { state.speed.unwrap_or_default() } 28 | { " timestamp: " } 29 | { state.timestamp } 30 | { " error: " } 31 | { &format!("{:?}", state.error) } 32 |

33 |
34 |
35 |
36 | } 37 | } 38 | -------------------------------------------------------------------------------- /examples/yew-app/src/app/hooks/use_hash.rs: -------------------------------------------------------------------------------- 1 | use yew::prelude::*; 2 | use yew_hooks::prelude::*; 3 | 4 | use crate::components::ui::button::Button; 5 | 6 | /// `use_hash` demo 7 | #[function_component] 8 | pub fn UseHash() -> Html { 9 | let hash = use_hash(); 10 | 11 | let onclick = { 12 | let hash = hash.clone(); 13 | Callback::from(move |_| { 14 | hash.set("#/path/to/page?userId=123".to_string()); 15 | }) 16 | }; 17 | 18 | html! { 19 |
20 |
21 |
22 |

23 | { " Current hash: " } 24 | { &*hash } 25 |

26 |

27 |
28 | {" or click a link "} { "#/path/to/page?userId=456" } 29 |

30 |
31 |
32 |
33 | } 34 | } 35 | -------------------------------------------------------------------------------- /examples/yew-app/src/app/hooks/use_hovered.rs: -------------------------------------------------------------------------------- 1 | use yew::prelude::*; 2 | use yew_hooks::prelude::*; 3 | 4 | /// `use_hovered` demo 5 | #[function_component] 6 | pub fn UseHovered() -> Html { 7 | let node = use_node_ref(); 8 | let hovered = use_hovered(node.clone()); 9 | 10 | html! { 11 |
12 |
13 |
14 |

15 | { " Hovered: " } 16 | { hovered } 17 |

18 |

19 | { "Try to hover your cursor over this element." } 20 |

21 |
22 |
23 |
24 | } 25 | } 26 | -------------------------------------------------------------------------------- /examples/yew-app/src/app/hooks/use_infinite_scroll.rs: -------------------------------------------------------------------------------- 1 | use yew::prelude::*; 2 | use yew_hooks::prelude::*; 3 | 4 | /// `use_infinite_scroll` demo 5 | #[function_component] 6 | pub fn UseInfiniteScroll() -> Html { 7 | let node = use_node_ref(); 8 | let state = use_list(vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); 9 | 10 | { 11 | let state = state.clone(); 12 | use_infinite_scroll(node.clone(), move || { 13 | let max = state.current().len() + 1; 14 | let mut more = vec![max, max + 1, max + 2, max + 3, max + 4, max + 5, max + 6]; 15 | state.append(&mut more); 16 | }); 17 | } 18 | 19 | html! { 20 |
21 |
22 |
23 |
24 |
25 | { "Try to scroll in this area vertically." } 26 | { 27 | for state.current().iter().map(|element| { 28 | html! {

{ element }

} 29 | }) 30 | } 31 | 32 |
33 |
34 |
35 |
36 |
37 | } 38 | } 39 | -------------------------------------------------------------------------------- /examples/yew-app/src/app/hooks/use_interval.rs: -------------------------------------------------------------------------------- 1 | use yew::prelude::*; 2 | use yew_hooks::prelude::*; 3 | 4 | use crate::components::ui::button::Button; 5 | 6 | /// `use_interval` demo 7 | #[function_component] 8 | pub fn UseInterval() -> Html { 9 | let millis = use_state(|| 0); 10 | let state = use_state(|| 0); 11 | 12 | let onstart = { 13 | let millis = millis.clone(); 14 | Callback::from(move |_| millis.set(2000)) 15 | }; 16 | let oncancel = { 17 | let millis = millis.clone(); 18 | Callback::from(move |_| millis.set(0)) 19 | }; 20 | 21 | { 22 | let state = state.clone(); 23 | use_interval( 24 | move || { 25 | state.set(*state + 1); 26 | }, 27 | *millis, 28 | ); 29 | } 30 | 31 | html! { 32 |
33 |
34 |
35 | 36 | 37 |

38 | { "Interval state: " } 39 | { *state } 40 |

41 |
42 |
43 |
44 | } 45 | } 46 | -------------------------------------------------------------------------------- /examples/yew-app/src/app/hooks/use_is_first_mount.rs: -------------------------------------------------------------------------------- 1 | use yew::prelude::*; 2 | use yew_hooks::prelude::*; 3 | 4 | /// `use_is_first_mount` demo 5 | #[function_component] 6 | pub fn UseIsFirstMount() -> Html { 7 | let is_first = use_is_first_mount(); 8 | 9 | html! { 10 |
11 |
12 |
13 |

14 | { "Is first mount: " } 15 | { is_first } 16 |

17 |
18 |
19 |
20 | } 21 | } 22 | -------------------------------------------------------------------------------- /examples/yew-app/src/app/hooks/use_is_mounted.rs: -------------------------------------------------------------------------------- 1 | use gloo::dialogs::alert; 2 | use yew::prelude::*; 3 | use yew_hooks::prelude::*; 4 | 5 | /// `use_is_mounted` demo 6 | #[function_component] 7 | pub fn UseIsMounted() -> Html { 8 | let is_mounted = use_is_mounted(); 9 | 10 | { 11 | let is_mounted = is_mounted.clone(); 12 | use_timeout( 13 | move || { 14 | alert(format!("Is mounted: {:?}", is_mounted()).as_str()); 15 | }, 16 | 2000, 17 | ); 18 | } 19 | 20 | html! { 21 |
22 |
23 |
24 |

25 | { "Is mounted: " } 26 | { is_mounted() } 27 |

28 |
29 |
30 |
31 | } 32 | } 33 | -------------------------------------------------------------------------------- /examples/yew-app/src/app/hooks/use_latest.rs: -------------------------------------------------------------------------------- 1 | use gloo::timers::callback::Interval; 2 | use yew::prelude::*; 3 | use yew_hooks::prelude::*; 4 | 5 | /// `use_latest` demo 6 | #[function_component] 7 | pub fn UseLatest() -> Html { 8 | let state = use_state(|| 0); 9 | let interval = use_mut_ref(|| None); 10 | 11 | let latest_state = use_latest(state.clone()); 12 | 13 | let state2 = use_state(|| 0); 14 | let interval2 = use_mut_ref(|| None); 15 | 16 | { 17 | let state = state.clone(); 18 | use_effect_once(move || { 19 | *interval.borrow_mut() = Some(Interval::new(1000, move || { 20 | // This will get the latest state and increase it by 1 each time. 21 | state.set(**latest_state.current() + 1); 22 | })); 23 | move || *interval.borrow_mut() = None 24 | }); 25 | } 26 | 27 | { 28 | let state2 = state2.clone(); 29 | use_effect_once(move || { 30 | *interval2.borrow_mut() = Some(Interval::new(1000, move || { 31 | // This will NOT get the latest state2 but always the initial 0 each time. 32 | state2.set(*state2 + 1); 33 | })); 34 | move || *interval2.borrow_mut() = None 35 | }); 36 | } 37 | 38 | html! { 39 |
40 |
41 |
42 |

43 | { "Latest value: " } 44 | { *state } 45 |

46 |

47 | { "Stale value: " } 48 | { *state2 } 49 |

50 |
51 |
52 |
53 | } 54 | } 55 | -------------------------------------------------------------------------------- /examples/yew-app/src/app/hooks/use_list.rs: -------------------------------------------------------------------------------- 1 | use yew::prelude::*; 2 | use yew_hooks::prelude::*; 3 | 4 | use crate::components::ui::button::Button; 5 | 6 | /// `use_list` demo 7 | #[function_component] 8 | pub fn UseList() -> Html { 9 | let list = use_list(vec![1, 2, 3, 4, 5]); 10 | 11 | let onset = { 12 | let list = list.clone(); 13 | Callback::from(move |_| list.set(vec![6, 7, 8, 9, 10])) 14 | }; 15 | let oninsert = { 16 | let list = list.clone(); 17 | Callback::from(move |_| list.insert(0, 9)) 18 | }; 19 | let onupdate = { 20 | let list = list.clone(); 21 | Callback::from(move |_| list.update(0, 4)) 22 | }; 23 | let onremove = { 24 | let list = list.clone(); 25 | Callback::from(move |_| { 26 | let _ = list.remove(0); 27 | }) 28 | }; 29 | let onpush = { 30 | let list = list.clone(); 31 | Callback::from(move |_| list.push(6)) 32 | }; 33 | let onpop = { 34 | let list = list.clone(); 35 | Callback::from(move |_| { 36 | let _ = list.pop(); 37 | }) 38 | }; 39 | let onretain = { 40 | let list = list.clone(); 41 | Callback::from(move |_| list.retain(|x| x % 2 == 0)) 42 | }; 43 | let onreverse = { 44 | let list = list.clone(); 45 | Callback::from(move |_| list.reverse()) 46 | }; 47 | let onappend = { 48 | let list = list.clone(); 49 | Callback::from(move |_| list.append(&mut vec![11, 12, 13])) 50 | }; 51 | let onsort = { 52 | let list = list.clone(); 53 | Callback::from(move |_| list.sort()) 54 | }; 55 | let onsortdesc = { 56 | let list = list.clone(); 57 | Callback::from(move |_| list.sort_by(|a, b| b.cmp(a))) 58 | }; 59 | let onswap = { 60 | let list = list.clone(); 61 | Callback::from(move |_| list.swap(0, 1)) 62 | }; 63 | let onclear = { 64 | let list = list.clone(); 65 | Callback::from(move |_| list.clear()) 66 | }; 67 | 68 | html! { 69 |
70 |
71 |
72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 |

86 | { "Current list: " } 87 |

88 |

89 | { 90 | for list.current().iter().map(|element| { 91 | html! { 92 | { element } 93 | } 94 | }) 95 | } 96 |

97 |
98 |
99 |
100 | } 101 | } 102 | -------------------------------------------------------------------------------- /examples/yew-app/src/app/hooks/use_local_storage.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | use yew::prelude::*; 3 | use yew_hooks::prelude::*; 4 | 5 | use crate::components::ui::button::Button; 6 | 7 | #[derive(Serialize, Deserialize, Clone)] 8 | struct User { 9 | name: String, 10 | token: String, 11 | } 12 | 13 | /// `use_local_storage` demo 14 | #[function_component] 15 | pub fn UseLocalStorage() -> Html { 16 | let storage = use_local_storage::("user".to_string()); 17 | 18 | let onclick = { 19 | let storage = storage.clone(); 20 | Callback::from(move |_| { 21 | storage.set(User { 22 | name: String::from("Jet Li"), 23 | token: String::from("jwt_token"), 24 | }); 25 | }) 26 | }; 27 | let ondelete = { 28 | let storage = storage.clone(); 29 | Callback::from(move |_| storage.delete()) 30 | }; 31 | 32 | html! { 33 |
34 |
35 |
36 | { 37 | (*storage).as_ref().map_or_else(|| html! { 38 | <> 39 | 40 | 41 | }, |user| html! { 42 | <> 43 | 44 |

45 | { "Current user: " } 46 | { &user.name } { " - " } { &user.token } 47 |

48 | 49 | }) 50 | } 51 |
52 |
53 |
54 | } 55 | } 56 | -------------------------------------------------------------------------------- /examples/yew-app/src/app/hooks/use_location.rs: -------------------------------------------------------------------------------- 1 | use yew::prelude::*; 2 | use yew_hooks::prelude::*; 3 | 4 | /// `use_location` demo 5 | #[function_component] 6 | pub fn UseLocation() -> Html { 7 | let location = use_location(); 8 | 9 | html! { 10 |
11 |
12 |
13 |

14 | { "trigger: " } 15 | { &location.trigger } 16 |

17 |

18 | { "state: " } 19 | { format!("{:?}", &location.state) } 20 |

21 |

22 | { "length: " } 23 | { &location.length } 24 |

25 |

26 | { "hash: " } 27 | { &location.hash } 28 |

29 |

30 | { "host: " } 31 | { &location.host } 32 |

33 |

34 | { "hostname: " } 35 | { &location.hostname } 36 |

37 |

38 | { "href: " } 39 | { &location.href } 40 |

41 |

42 | { "origin: " } 43 | { &location.origin } 44 |

45 |

46 | { "pathname: " } 47 | { &location.pathname } 48 |

49 |

50 | { "port: " } 51 | { &location.port } 52 |

53 |

54 | { "protocol: " } 55 | { &location.protocol } 56 |

57 |

58 | { "search: " } 59 | { &location.search } 60 |

61 |
62 |
63 |
64 | } 65 | } 66 | -------------------------------------------------------------------------------- /examples/yew-app/src/app/hooks/use_logger.rs: -------------------------------------------------------------------------------- 1 | use yew::prelude::*; 2 | use yew_hooks::prelude::*; 3 | 4 | use crate::components::ui::button::Button; 5 | 6 | /// `use_logger` demo 7 | #[function_component(MyComponent)] 8 | fn my_component(props: &MyComponentProps) -> Html { 9 | // Demo #1, logs whenever anything is updated. 10 | use_logger("MyComponent Props".to_string(), props.clone()); 11 | 12 | let counter = use_counter(0); 13 | // Demo #2, logs only when `prev_state != next_state`. 14 | use_logger_eq("MyComponent States".to_string(), counter.clone()); 15 | 16 | let onincrease = { 17 | let counter = counter.clone(); 18 | Callback::from(move |_| counter.increase()) 19 | }; 20 | 21 | html! { 22 | <> 23 | { "My Component" } 24 |

25 |
26 | { " counter: " } { props.counter } 27 | { " internal counter: " } { *counter } 28 | { " title: " } { &props.title } 29 |

30 | 31 | } 32 | } 33 | 34 | #[derive(Debug, Properties, PartialEq, Clone)] 35 | struct MyComponentProps { 36 | pub counter: i32, 37 | pub title: String, 38 | } 39 | 40 | #[function_component(UseLogger)] 41 | pub fn logger() -> Html { 42 | let toggle = use_toggle("Mount", "Unmount"); 43 | let counter = use_counter(0); 44 | 45 | let onclick = { 46 | let toggle = toggle.clone(); 47 | Callback::from(move |_| toggle.toggle()) 48 | }; 49 | 50 | let onincrease = { 51 | let counter = counter.clone(); 52 | Callback::from(move |_| counter.increase()) 53 | }; 54 | 55 | html! { 56 |
57 |
58 |
59 | 60 | 61 |

62 | { 63 | if *toggle == "Unmount" { 64 | html! { } 65 | } else { 66 | html! {} 67 | } 68 | } 69 |

70 |

71 | { "Please open the browser console to view the output!" } 72 |

73 |
74 |
75 |
76 | } 77 | } 78 | -------------------------------------------------------------------------------- /examples/yew-app/src/app/hooks/use_map.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use yew::prelude::*; 4 | use yew_hooks::prelude::*; 5 | 6 | use crate::components::ui::button::Button; 7 | 8 | /// `use_map` demo 9 | #[function_component] 10 | pub fn UseMap() -> Html { 11 | let map = use_map(HashMap::from([ 12 | ("Mercury", 0.4), 13 | ("Venus", 0.7), 14 | ("Earth", 1.0), 15 | ("Mars", 1.5), 16 | ])); 17 | 18 | let onset = { 19 | let map = map.clone(); 20 | Callback::from(move |_| map.set(HashMap::from([("Moon", 0.8), ("Earth", 1.0)]))) 21 | }; 22 | let oninsert = { 23 | let map = map.clone(); 24 | Callback::from(move |_| { 25 | let _ = map.insert("Jupiter", 2.1); 26 | }) 27 | }; 28 | let onupdate = { 29 | let map = map.clone(); 30 | Callback::from(move |_| map.update(&"Earth", 1.1)) 31 | }; 32 | let onremove = { 33 | let map = map.clone(); 34 | Callback::from(move |_| { 35 | let _ = map.remove(&"Moon"); 36 | }) 37 | }; 38 | let onretain = { 39 | let map = map.clone(); 40 | Callback::from(move |_| map.retain(|_k, v| v > &mut 1.0)) 41 | }; 42 | let onclear = { 43 | let map = map.clone(); 44 | Callback::from(move |_| map.clear()) 45 | }; 46 | 47 | html! { 48 |
49 |
50 |
51 | 52 | 53 | 54 | 55 | 56 | 57 |

58 | { "Current map: " } 59 |

60 | { 61 | for map.current().iter().map(|(k, v)| { 62 | html! { 63 |

{ k } {": "} { v }

64 | } 65 | }) 66 | } 67 |
68 |
69 |
70 | } 71 | } 72 | -------------------------------------------------------------------------------- /examples/yew-app/src/app/hooks/use_measure.rs: -------------------------------------------------------------------------------- 1 | use yew::prelude::*; 2 | use yew_hooks::prelude::*; 3 | 4 | /// `use_measure` demo 5 | #[function_component] 6 | pub fn UseMeasure() -> Html { 7 | let node = use_node_ref(); 8 | let state = use_measure(node.clone()); 9 | 10 | html! { 11 |
12 |
13 |
14 |

15 | { " X: " } 16 | { state.x } 17 | { " Y: " } 18 | { state.y } 19 | { " Width: " } 20 | { state.width } 21 | { " Height: " } 22 | { state.height } 23 | { " Top: " } 24 | { state.top } 25 | { " Left: " } 26 | { state.left } 27 | { " Bottom: " } 28 | { state.bottom } 29 | { " Right: " } 30 | { state.right } 31 |

32 |

33 | { "Try to resize the window of your browser." } 34 |

35 |
36 |
37 |
38 | } 39 | } 40 | -------------------------------------------------------------------------------- /examples/yew-app/src/app/hooks/use_media.rs: -------------------------------------------------------------------------------- 1 | use yew::prelude::*; 2 | use yew_hooks::prelude::*; 3 | 4 | use crate::components::ui::button::Button; 5 | 6 | /// `use_media` demo 7 | #[function_component] 8 | pub fn UseMedia() -> Html { 9 | let node_video = use_node_ref(); 10 | let node_audio = use_node_ref(); 11 | 12 | let video = use_media( 13 | node_video.clone(), 14 | "http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4".to_string(), 15 | ); 16 | 17 | let audio = use_media_with_options( 18 | node_audio.clone(), 19 | "https://www.soundhelix.com/examples/mp3/SoundHelix-Song-2.mp3".to_string(), 20 | UseMediaOptions::enable_auto_play(), 21 | ); 22 | 23 | let onplay = { 24 | let video = video.clone(); 25 | Callback::from(move |_| video.play()) 26 | }; 27 | 28 | let onpause = { 29 | let video = video.clone(); 30 | Callback::from(move |_| video.pause()) 31 | }; 32 | 33 | let onmute = { 34 | let video = video.clone(); 35 | Callback::from(move |_| video.mute()) 36 | }; 37 | 38 | let onunmute = { 39 | let video = video.clone(); 40 | Callback::from(move |_| video.unmute()) 41 | }; 42 | 43 | let onvol1 = { 44 | let video = video.clone(); 45 | Callback::from(move |_| video.set_volume(0.1)) 46 | }; 47 | 48 | let onvol2 = { 49 | let video = video.clone(); 50 | Callback::from(move |_| video.set_volume(0.5)) 51 | }; 52 | 53 | let onvol3 = { 54 | let video = video.clone(); 55 | Callback::from(move |_| video.set_volume(1.0)) 56 | }; 57 | 58 | let onseek1 = { 59 | let video = video.clone(); 60 | Callback::from(move |_| video.seek(*video.time - 5.0)) 61 | }; 62 | 63 | let onseek2 = { 64 | let video = video.clone(); 65 | Callback::from(move |_| video.seek(*video.time + 5.0)) 66 | }; 67 | 68 | let onplay_audio = { 69 | let audio = audio.clone(); 70 | Callback::from(move |_| audio.play()) 71 | }; 72 | 73 | let onpause_audio = { 74 | let audio = audio.clone(); 75 | Callback::from(move |_| audio.pause()) 76 | }; 77 | 78 | html! { 79 |
80 |
81 |
82 |

83 | { " Video " } 84 | { " Duration: " } { *video.duration } 85 | { " Time: " } { *video.time } 86 | { " Volume: " } { *video.volume } 87 |

88 |

89 |

90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 |

100 |

{ " Audio " }

101 |

102 |

103 | 104 | 105 |

106 |
107 |
108 |
109 | } 110 | } 111 | -------------------------------------------------------------------------------- /examples/yew-app/src/app/hooks/use_mount.rs: -------------------------------------------------------------------------------- 1 | use gloo::dialogs::alert; 2 | use yew::prelude::*; 3 | use yew_hooks::prelude::*; 4 | 5 | use crate::components::ui::button::Button; 6 | 7 | /// `use_mount` demo 8 | #[function_component] 9 | fn MyComponent() -> Html { 10 | use_mount(|| { 11 | alert("Mount!"); 12 | }); 13 | 14 | html! { 15 | <>{ "My Component" } 16 | } 17 | } 18 | 19 | #[function_component] 20 | pub fn UseMount() -> Html { 21 | let toggle = use_toggle("Mount", "Unmount"); 22 | 23 | let onclick = { 24 | let toggle = toggle.clone(); 25 | Callback::from(move |_| toggle.toggle()) 26 | }; 27 | 28 | html! { 29 |
30 |
31 |
32 | 33 |

34 | { 35 | if *toggle == "Unmount" { 36 | html! { } 37 | } else { 38 | html! {} 39 | } 40 | } 41 |

42 |
43 |
44 |
45 | } 46 | } 47 | -------------------------------------------------------------------------------- /examples/yew-app/src/app/hooks/use_mut_latest.rs: -------------------------------------------------------------------------------- 1 | use gloo::timers::callback::Interval; 2 | use yew::prelude::*; 3 | use yew_hooks::prelude::*; 4 | 5 | /// `use_mut_latest` demo 6 | #[function_component] 7 | pub fn UseMutLatest() -> Html { 8 | let state = use_state(|| 0); 9 | let interval = use_mut_ref(|| None); 10 | let closure = { 11 | let state = state.clone(); 12 | move || state.set(*state + 1) 13 | }; 14 | 15 | let latest_closure = use_mut_latest(closure); 16 | 17 | let state2 = use_state(|| 0); 18 | let interval2 = use_mut_ref(|| None); 19 | let closure2 = { 20 | let state2 = state2.clone(); 21 | move || state2.set(*state2 + 1) 22 | }; 23 | 24 | use_effect_once(move || { 25 | *interval.borrow_mut() = Some(Interval::new(1000, move || { 26 | let latest_closure = latest_closure.current(); 27 | let closure = &*latest_closure.borrow_mut(); 28 | // This will get the latest closure and increase state by 1 each time. 29 | closure(); 30 | })); 31 | move || *interval.borrow_mut() = None 32 | }); 33 | 34 | use_effect_once(move || { 35 | *interval2.borrow_mut() = Some(Interval::new(1000, move || { 36 | // This will NOT get the latest closure2 but always the initial 0 each time. 37 | closure2(); 38 | })); 39 | move || *interval2.borrow_mut() = None 40 | }); 41 | 42 | html! { 43 |
44 |
45 |
46 |

47 | { "Latest value: " } 48 | { *state } 49 |

50 |

51 | { "Stale value: " } 52 | { *state2 } 53 |

54 |
55 |
56 |
57 | } 58 | } 59 | -------------------------------------------------------------------------------- /examples/yew-app/src/app/hooks/use_previous.rs: -------------------------------------------------------------------------------- 1 | use yew::prelude::*; 2 | use yew_hooks::prelude::*; 3 | 4 | use crate::components::ui::button::Button; 5 | 6 | /// `use_previous` demo 7 | #[function_component] 8 | pub fn UsePrevious() -> Html { 9 | let state = use_state(|| 0); 10 | let previous_state = use_previous(state.clone()); 11 | 12 | let onincrease = { 13 | let state = state.clone(); 14 | Callback::from(move |_| state.set(*state + 1)) 15 | }; 16 | let ondecrease = { 17 | let state = state.clone(); 18 | Callback::from(move |_| state.set(*state - 1)) 19 | }; 20 | 21 | html! { 22 |
23 |
24 |
25 | 26 | 27 |

28 | { "Current value: " } 29 | { *state } 30 |

31 |

32 | { "Previous value: " } 33 | { **previous_state } 34 |

35 |
36 |
37 |
38 | } 39 | } 40 | -------------------------------------------------------------------------------- /examples/yew-app/src/app/hooks/use_queue.rs: -------------------------------------------------------------------------------- 1 | use std::collections::VecDeque; 2 | 3 | use yew::prelude::*; 4 | use yew_hooks::prelude::*; 5 | 6 | use crate::components::ui::button::Button; 7 | 8 | /// `use_queue` demo 9 | #[function_component] 10 | pub fn UseQueue() -> Html { 11 | let queue = use_queue(VecDeque::from(["Mercury", "Venus", "Earth", "Mars"])); 12 | 13 | let onset = { 14 | let queue = queue.clone(); 15 | Callback::from(move |_| queue.set(VecDeque::from(["Moon", "Earth"]))) 16 | }; 17 | let onpush_back = { 18 | let queue = queue.clone(); 19 | Callback::from(move |_| { 20 | queue.push_back("Jupiter"); 21 | }) 22 | }; 23 | let onpop_front = { 24 | let queue = queue.clone(); 25 | Callback::from(move |_| { 26 | let _ = queue.pop_front(); 27 | }) 28 | }; 29 | let onretain = { 30 | let queue = queue.clone(); 31 | Callback::from(move |_| queue.retain(|v| v.contains('a'))) 32 | }; 33 | let onclear = { 34 | let queue = queue.clone(); 35 | Callback::from(move |_| queue.clear()) 36 | }; 37 | 38 | html! { 39 |
40 |
41 |
42 | 43 | 44 | 45 | 46 | 47 |

48 | { "Current queue: " } 49 |

50 | { 51 | for queue.current().iter().map(|v| { 52 | html! { 53 |

{ v }

54 | } 55 | }) 56 | } 57 |
58 |
59 |
60 | } 61 | } 62 | -------------------------------------------------------------------------------- /examples/yew-app/src/app/hooks/use_raf.rs: -------------------------------------------------------------------------------- 1 | use yew::prelude::*; 2 | use yew_hooks::prelude::*; 3 | 4 | /// `use_raf` demo 5 | #[function_component] 6 | pub fn UseRaf() -> Html { 7 | let elapsed = use_raf(5000, 1000); 8 | 9 | html! { 10 |
11 |
12 |
13 |

14 | { "Elapsed: " }
15 | { elapsed } 16 |

17 |
18 |
19 |
20 | } 21 | } 22 | -------------------------------------------------------------------------------- /examples/yew-app/src/app/hooks/use_raf_state.rs: -------------------------------------------------------------------------------- 1 | use web_sys::{window, Window}; 2 | use yew::prelude::*; 3 | use yew_hooks::prelude::*; 4 | 5 | /// `use_raf_state` demo 6 | #[function_component] 7 | pub fn UseRafState() -> Html { 8 | let state = use_raf_state(|| { 9 | ( 10 | window().unwrap().inner_width().unwrap().as_f64().unwrap(), 11 | window().unwrap().inner_height().unwrap().as_f64().unwrap(), 12 | ) 13 | }); 14 | 15 | { 16 | let state = state.clone(); 17 | use_event_with_window("resize", move |e: Event| { 18 | let window: Window = e.target_unchecked_into(); 19 | state.set(( 20 | window.inner_width().unwrap().as_f64().unwrap(), 21 | window.inner_height().unwrap().as_f64().unwrap(), 22 | )); 23 | }); 24 | } 25 | 26 | html! { 27 |
28 |
29 |
30 |

31 | { " Width: " } 32 | { state.0 } 33 | { " Height: " } 34 | { state.1 } 35 |

36 |

37 | { "Try to resize the window of your browser." } 38 |

39 |
40 |
41 |
42 | } 43 | } 44 | -------------------------------------------------------------------------------- /examples/yew-app/src/app/hooks/use_renders_count.rs: -------------------------------------------------------------------------------- 1 | use yew::prelude::*; 2 | use yew_hooks::prelude::*; 3 | 4 | use crate::components::ui::button::Button; 5 | 6 | /// `use_renders_count` demo 7 | #[function_component] 8 | pub fn UseRendersCount() -> Html { 9 | let count = use_renders_count(); 10 | let update = use_update(); 11 | 12 | let onclick = Callback::from(move |_| { 13 | update(); 14 | }); 15 | 16 | html! { 17 |
18 |
19 |
20 | 21 |

22 | { "Count: " } 23 | { count } 24 |

25 |
26 |
27 |
28 | } 29 | } 30 | -------------------------------------------------------------------------------- /examples/yew-app/src/app/hooks/use_scroll.rs: -------------------------------------------------------------------------------- 1 | use yew::prelude::*; 2 | use yew_hooks::prelude::*; 3 | 4 | /// `use_scroll` demo 5 | #[function_component] 6 | pub fn UseScroll() -> Html { 7 | let node = use_node_ref(); 8 | let state = use_scroll(node.clone()); 9 | 10 | html! { 11 |
12 |
13 |
14 |

15 | { " X: " } 16 | { state.0 } 17 | { " Y: " } 18 | { state.1 } 19 |

20 |
21 |
22 | { "Try to scroll in this area vertically or horizontally." } 23 |
24 |
25 |
26 |
27 |
28 | } 29 | } 30 | -------------------------------------------------------------------------------- /examples/yew-app/src/app/hooks/use_scrolling.rs: -------------------------------------------------------------------------------- 1 | use yew::prelude::*; 2 | use yew_hooks::prelude::*; 3 | 4 | /// `use_scrolling` demo 5 | #[function_component] 6 | pub fn UseScrolling() -> Html { 7 | let node = use_node_ref(); 8 | let state = use_scrolling(node.clone()); 9 | 10 | html! { 11 |
12 |
13 |
14 |

15 | { " Scrolling: " } 16 | { if state { "Scrolling" } else { "Not scrolling" } } 17 |

18 |
19 |
20 | { "Try to scroll in this area vertically or horizontally." } 21 |
22 |
23 |
24 |
25 |
26 | } 27 | } 28 | -------------------------------------------------------------------------------- /examples/yew-app/src/app/hooks/use_search_param.rs: -------------------------------------------------------------------------------- 1 | use yew::prelude::*; 2 | use yew_hooks::prelude::*; 3 | 4 | /// `use_search_param` demo 5 | #[function_component] 6 | pub fn UseSearchParam() -> Html { 7 | let param = use_search_param("foo".to_string()); 8 | 9 | html! { 10 |
11 |
12 |
13 |

14 | { " Current search param: " } 15 | { param.unwrap_or_default() } 16 |

17 |

18 | {" click this link "} { "?foo=123" } 19 |

20 |
21 |
22 |
23 | } 24 | } 25 | -------------------------------------------------------------------------------- /examples/yew-app/src/app/hooks/use_session_storage.rs: -------------------------------------------------------------------------------- 1 | use yew::prelude::*; 2 | use yew_hooks::prelude::*; 3 | 4 | use crate::components::ui::button::Button; 5 | 6 | /// `use_session_storage` demo 7 | #[function_component] 8 | pub fn UseSessionStorage() -> Html { 9 | let storage = use_session_storage::("foo".to_string()); 10 | 11 | let onclick = { 12 | let storage = storage.clone(); 13 | Callback::from(move |_| storage.set("bar".to_string())) 14 | }; 15 | let ondelete = { 16 | let storage = storage.clone(); 17 | Callback::from(move |_| storage.delete()) 18 | }; 19 | 20 | html! { 21 |
22 |
23 |
24 | 25 | 26 |

27 | { "Current value: " } 28 | { 29 | (*storage) 30 | .as_ref() 31 | .map_or_else(|| html! {}, |value| html! { value }) 32 | } 33 |

34 |
35 |
36 |
37 | } 38 | } 39 | -------------------------------------------------------------------------------- /examples/yew-app/src/app/hooks/use_set.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashSet; 2 | 3 | use yew::prelude::*; 4 | use yew_hooks::prelude::*; 5 | 6 | use crate::components::ui::button::Button; 7 | 8 | /// `use_set` demo 9 | #[function_component] 10 | pub fn UseSet() -> Html { 11 | let set = use_set(HashSet::from(["Mercury", "Venus", "Earth", "Mars"])); 12 | 13 | let onset = { 14 | let set = set.clone(); 15 | Callback::from(move |_| set.set(HashSet::from(["Moon", "Earth"]))) 16 | }; 17 | let oninsert = { 18 | let set = set.clone(); 19 | Callback::from(move |_| { 20 | let _ = set.insert("Jupiter"); 21 | }) 22 | }; 23 | let onreplace = { 24 | let set = set.clone(); 25 | Callback::from(move |_| { 26 | let _ = set.replace("Earth"); 27 | }) 28 | }; 29 | let onremove = { 30 | let set = set.clone(); 31 | Callback::from(move |_| { 32 | let _ = set.remove(&"Moon"); 33 | }) 34 | }; 35 | let onretain = { 36 | let set = set.clone(); 37 | Callback::from(move |_| set.retain(|v| v.contains('a'))) 38 | }; 39 | let onclear = { 40 | let set = set.clone(); 41 | Callback::from(move |_| set.clear()) 42 | }; 43 | 44 | html! { 45 |
46 |
47 |
48 | 49 | 50 | 51 | 52 | 53 | 54 |

55 | { "Current set: " } 56 |

57 | { 58 | for set.current().iter().map(|v| { 59 | html! { 60 |

{ v }

61 | } 62 | }) 63 | } 64 |
65 |
66 |
67 | } 68 | } 69 | -------------------------------------------------------------------------------- /examples/yew-app/src/app/hooks/use_size.rs: -------------------------------------------------------------------------------- 1 | use yew::prelude::*; 2 | use yew_hooks::prelude::*; 3 | 4 | /// `use_size` demo 5 | #[function_component] 6 | pub fn UseSize() -> Html { 7 | let node = use_node_ref(); 8 | let state = use_size(node.clone()); 9 | 10 | html! { 11 |
12 |
13 |
14 |

15 | { " Width: " } 16 | { state.0 } 17 | { " Height: " } 18 | { state.1 } 19 |

20 |

21 | { "Try to resize the window of your browser." } 22 |

23 |
24 |
25 |
26 | } 27 | } 28 | -------------------------------------------------------------------------------- /examples/yew-app/src/app/hooks/use_state_ptr_eq.rs: -------------------------------------------------------------------------------- 1 | use yew::prelude::*; 2 | use yew_hooks::prelude::*; 3 | 4 | use crate::components::ui::button::Button; 5 | 6 | /// `use_state_ptr_eq` demo 7 | #[function_component] 8 | pub fn UseStatePtrEq() -> Html { 9 | let state = use_state_ptr_eq(String::new); 10 | let history = use_list(vec![]); 11 | 12 | let onclick = { 13 | let state = state.clone(); 14 | // Set the state to the same string `Hello, world!` every time. 15 | Callback::from(move |_| state.set("Hello, world!".to_string())) 16 | }; 17 | 18 | { 19 | let history = history.clone(); 20 | // This effect will not run if use `use_state` or `use_state_eq`. 21 | use_effect_with(state, move |message| { 22 | history.push((**message).clone()); 23 | 24 | || () 25 | }); 26 | } 27 | 28 | html! { 29 |
30 |
31 |
32 | 33 |

34 | { "Message history: " } 35 |

36 | { 37 | for history.current().iter().map(|message| { 38 | html! { 39 |

{ message }

40 | } 41 | }) 42 | } 43 |
44 |
45 |
46 | } 47 | } 48 | -------------------------------------------------------------------------------- /examples/yew-app/src/app/hooks/use_swipe.rs: -------------------------------------------------------------------------------- 1 | use yew::prelude::*; 2 | use yew_hooks::prelude::*; 3 | 4 | /// `use_swipe` demo 5 | #[function_component] 6 | pub fn UseSwipe() -> Html { 7 | let node = use_node_ref(); 8 | // Demo #1, detect swipe for a node. 9 | let state = use_swipe(node.clone()); 10 | 11 | // You can depend on direction/swiping etc. 12 | { 13 | let state = state.clone(); 14 | use_effect_with(state.direction, move |direction| { 15 | // Do something based on direction. 16 | match **direction { 17 | UseSwipeDirection::Left => (), 18 | UseSwipeDirection::Right => (), 19 | UseSwipeDirection::Up => (), 20 | UseSwipeDirection::Down => (), 21 | _ => (), 22 | } 23 | || () 24 | }); 25 | } 26 | 27 | // Demo #2, detect swipe for window with options, or use `use_swipe_with_window`. 28 | let _ = use_swipe_with_options( 29 | NodeRef::default(), 30 | UseSwipeOptions { 31 | onswipeend: Some(Box::new(move |_e, direction| { 32 | // Deal with TouchEvent. 33 | log::debug!("Swipe direction: {:?}", direction); 34 | })), 35 | ..Default::default() 36 | }, 37 | ); 38 | 39 | html! { 40 |
41 |
42 |
43 |

44 | { " swiping: " } 45 | { *state.swiping } 46 | { " direction: " } 47 | { format!("{:?}", *state.direction) } 48 | { " coords_start: " } 49 | { format!("{:?}", *state.coords_start) } 50 | { " coords_end: " } 51 | { format!("{:?}", *state.coords_end) } 52 | { " length_x: " } 53 | { *state.length_x } 54 | { " length_y: " } 55 | { *state.length_y } 56 |

57 |

58 | { "Try to swipe inside this area." } 59 |

60 |
61 |
62 |
63 | } 64 | } 65 | -------------------------------------------------------------------------------- /examples/yew-app/src/app/hooks/use_throttle.rs: -------------------------------------------------------------------------------- 1 | use yew::prelude::*; 2 | use yew_hooks::prelude::*; 3 | 4 | use crate::components::ui::button::Button; 5 | 6 | /// `use_throttle` demo 7 | #[function_component] 8 | pub fn UseThrottle() -> Html { 9 | let state = use_state(|| 0); 10 | 11 | let throttle = { 12 | let state = state.clone(); 13 | use_throttle( 14 | move || { 15 | state.set(*state + 1); 16 | }, 17 | 2000, 18 | ) 19 | }; 20 | 21 | let onclick = { 22 | let throttle = throttle.clone(); 23 | Callback::from(move |_| throttle.run()) 24 | }; 25 | 26 | let oncancel = { Callback::from(move |_| throttle.cancel()) }; 27 | 28 | html! { 29 |
30 |
31 |
32 | 33 | 34 |

35 | { "State: " } {*state} 36 |

37 |
38 |
39 |
40 | } 41 | } 42 | -------------------------------------------------------------------------------- /examples/yew-app/src/app/hooks/use_throttle_effect.rs: -------------------------------------------------------------------------------- 1 | use yew::prelude::*; 2 | use yew_hooks::prelude::*; 3 | 4 | use crate::components::ui::button::Button; 5 | 6 | /// `use_throttle_effect` demo 7 | #[function_component] 8 | pub fn UseThrottleEffect() -> Html { 9 | let state = use_state(|| 0); 10 | let update = use_update(); 11 | 12 | { 13 | let state = state.clone(); 14 | use_throttle_effect( 15 | move || { 16 | state.set(*state + 1); 17 | }, 18 | 2000, 19 | ); 20 | }; 21 | 22 | let onclick = { Callback::from(move |_| update()) }; 23 | 24 | html! { 25 |
26 |
27 |
28 | 29 |

30 | { "State: " } {*state} 31 |

32 |
33 |
34 |
35 | } 36 | } 37 | -------------------------------------------------------------------------------- /examples/yew-app/src/app/hooks/use_throttle_state.rs: -------------------------------------------------------------------------------- 1 | use yew::prelude::*; 2 | use yew_hooks::prelude::*; 3 | 4 | use crate::components::ui::button::Button; 5 | 6 | /// `use_throttle_state` demo 7 | #[function_component] 8 | pub fn UseThrottleState() -> Html { 9 | let state = use_throttle_state(|| 0, 2000); 10 | 11 | let onclick = { 12 | let state = state.clone(); 13 | Callback::from(move |_| state.set(*state + 1)) 14 | }; 15 | 16 | html! { 17 |
18 |
19 |
20 | 21 |

22 | { "State: " } {*state} 23 |

24 |
25 |
26 |
27 | } 28 | } 29 | -------------------------------------------------------------------------------- /examples/yew-app/src/app/hooks/use_timeout.rs: -------------------------------------------------------------------------------- 1 | use yew::prelude::*; 2 | use yew_hooks::prelude::*; 3 | 4 | use crate::components::ui::button::Button; 5 | 6 | /// `use_timeout` demo 7 | #[function_component] 8 | pub fn UseTimeout() -> Html { 9 | let state = use_state(|| 0); 10 | let millis = use_state(|| 2000); 11 | 12 | let timeout = { 13 | let state = state.clone(); 14 | let millis = millis.clone(); 15 | use_timeout( 16 | move || { 17 | state.set(*state + 1); 18 | }, 19 | *millis, 20 | ) 21 | }; 22 | 23 | let onreset = { 24 | let timeout = timeout.clone(); 25 | Callback::from(move |_| timeout.reset()) 26 | }; 27 | 28 | let oncancel = { Callback::from(move |_| timeout.cancel()) }; 29 | 30 | let onincrease = { Callback::from(move |_| millis.set(*millis + 1000)) }; 31 | 32 | html! { 33 |
34 |
35 |
36 | 37 | 38 | 39 |

40 | { "Timeout state: " } 41 | { *state } 42 |

43 |
44 |
45 |
46 | } 47 | } 48 | -------------------------------------------------------------------------------- /examples/yew-app/src/app/hooks/use_title.rs: -------------------------------------------------------------------------------- 1 | use yew::prelude::*; 2 | use yew_hooks::prelude::*; 3 | 4 | use crate::components::ui::button::Button; 5 | 6 | /// `use_title` demo 7 | #[function_component] 8 | fn MyComponent() -> Html { 9 | use_title("This is an awesome title".to_string()); 10 | 11 | html! { 12 | <>{ "My Component" } 13 | } 14 | } 15 | 16 | #[function_component] 17 | pub fn UseTitle() -> Html { 18 | let toggle = use_toggle("Mount to change title", "Unmount to restore title"); 19 | 20 | let onclick = { 21 | let toggle = toggle.clone(); 22 | Callback::from(move |_| toggle.toggle()) 23 | }; 24 | 25 | html! { 26 |
27 |
28 |
29 | 30 |

31 | { 32 | if *toggle == "Unmount to restore title" { 33 | html! { } 34 | } else { 35 | html! {} 36 | } 37 | } 38 |

39 |
40 |
41 |
42 | } 43 | } 44 | -------------------------------------------------------------------------------- /examples/yew-app/src/app/hooks/use_toggle.rs: -------------------------------------------------------------------------------- 1 | use yew::prelude::*; 2 | use yew_hooks::prelude::*; 3 | 4 | use crate::components::ui::button::Button; 5 | 6 | /// `use_toggle` demo 7 | #[function_component] 8 | pub fn UseToggle() -> Html { 9 | let toggle = use_toggle("Hello", "World"); 10 | 11 | let onclick = { 12 | let toggle = toggle.clone(); 13 | Callback::from(move |_| toggle.toggle()) 14 | }; 15 | let onreset = { 16 | let toggle = toggle.clone(); 17 | Callback::from(move |_| toggle.reset()) 18 | }; 19 | let onset = { 20 | let toggle = toggle.clone(); 21 | Callback::from(move |_| toggle.set("World")) 22 | }; 23 | 24 | html! { 25 |
26 |
27 |
28 | 29 | 30 | 31 |

32 | { "Current value: " } 33 | { *toggle } 34 |

35 |
36 |
37 |
38 | } 39 | } 40 | -------------------------------------------------------------------------------- /examples/yew-app/src/app/hooks/use_unmount.rs: -------------------------------------------------------------------------------- 1 | use gloo::dialogs::alert; 2 | use yew::prelude::*; 3 | use yew_hooks::prelude::*; 4 | 5 | use crate::components::ui::button::Button; 6 | 7 | /// `use_unmount` demo 8 | #[function_component] 9 | fn MyComponent() -> Html { 10 | use_unmount(|| { 11 | alert("Unmount!"); 12 | }); 13 | 14 | html! { 15 | <>{ "My Component" } 16 | } 17 | } 18 | 19 | #[function_component] 20 | pub fn UseUnmount() -> Html { 21 | let toggle = use_toggle("Unmount", "Mount"); 22 | 23 | let onclick = { 24 | let toggle = toggle.clone(); 25 | Callback::from(move |_| toggle.toggle()) 26 | }; 27 | 28 | html! { 29 |
30 |
31 |
32 | 33 |

34 | { 35 | if *toggle == "Unmount" { 36 | html! { } 37 | } else { 38 | html! {} 39 | } 40 | } 41 |

42 |
43 |
44 |
45 | } 46 | } 47 | -------------------------------------------------------------------------------- /examples/yew-app/src/app/hooks/use_update.rs: -------------------------------------------------------------------------------- 1 | use yew::prelude::*; 2 | use yew_hooks::prelude::*; 3 | 4 | use crate::components::ui::button::Button; 5 | 6 | /// `use_update` demo 7 | #[function_component] 8 | pub fn UseUpdate() -> Html { 9 | let update = use_update(); 10 | 11 | let onclick = Callback::from(move |_| { 12 | update(); 13 | }); 14 | 15 | html! { 16 |
17 |
18 |
19 | 20 |

21 | { "Current time: " } 22 | { get_current_time() } 23 |

24 |
25 |
26 |
27 | } 28 | } 29 | 30 | fn get_current_time() -> String { 31 | let date = js_sys::Date::new_0(); 32 | String::from(date.to_locale_time_string("en-US")) 33 | } 34 | -------------------------------------------------------------------------------- /examples/yew-app/src/app/hooks/use_visible.rs: -------------------------------------------------------------------------------- 1 | use yew::prelude::*; 2 | use yew_hooks::prelude::*; 3 | 4 | /// `use_visible` demo 5 | #[function_component] 6 | pub fn UseVisible() -> Html { 7 | let node = use_node_ref(); 8 | let visible = use_visible(node.clone(), false); 9 | 10 | html! { 11 |
12 |
13 |
14 |

15 | { " Visible: " } 16 | { visible } 17 |

18 |
19 |
20 |
{ "Try to scroll in this area." }
21 |
22 |
23 |
24 |
25 |
26 |
27 | } 28 | } 29 | -------------------------------------------------------------------------------- /examples/yew-app/src/app/hooks/use_websocket.rs: -------------------------------------------------------------------------------- 1 | use yew::prelude::*; 2 | use yew_hooks::prelude::*; 3 | 4 | use crate::components::ui::button::Button; 5 | 6 | /// `use_websocket` demo 7 | #[function_component] 8 | pub fn UseWebSocket() -> Html { 9 | let history = use_list(vec![]); 10 | 11 | // Demo #1, auto connect to websocket by default. 12 | let ws = use_websocket("wss://echo.websocket.events/".to_string()); 13 | let onclick = { 14 | let ws = ws.clone(); 15 | let history = history.clone(); 16 | Callback::from(move |_| { 17 | let message = "Hello, world!".to_string(); 18 | ws.send(message.clone()); 19 | history.push(format!("ws1 [send]: {}", message)); 20 | }) 21 | }; 22 | { 23 | let history = history.clone(); 24 | let ws = ws.clone(); 25 | // Receive message by depending on `ws.message`. 26 | use_effect_with(ws.message, move |message| { 27 | if let Some(message) = &**message { 28 | history.push(format!("ws1 [recv]: {}", message.clone())); 29 | } 30 | || () 31 | }); 32 | } 33 | 34 | // Demo #2, send bytes to websocket. 35 | let onclick_bytes = { 36 | let ws = ws.clone(); 37 | let history = history.clone(); 38 | Callback::from(move |_| { 39 | let message = b"Hello, world!\r\n".to_vec(); 40 | ws.send_bytes(message.clone()); 41 | history.push(format!("ws1 [send]: bytes {:?}", message)); 42 | }) 43 | }; 44 | { 45 | let history = history.clone(); 46 | let ws = ws.clone(); 47 | // Receive message by depending on `ws.message_bytes`. 48 | use_effect_with(ws.message_bytes, move |message| { 49 | if let Some(message) = &**message { 50 | history.push(format!("ws1 [recv]: bytes {:?}", message.clone())); 51 | } 52 | || () 53 | }); 54 | } 55 | 56 | // Demo #3, manually connect to websocket with custom options. 57 | let ws2 = { 58 | let history = history.clone(); 59 | use_websocket_with_options( 60 | "wss://echo.websocket.events/".to_string(), 61 | UseWebSocketOptions { 62 | // Receive message by callback `onmessage`. 63 | onmessage: Some(Box::new(move |message| { 64 | history.push(format!("ws2 [recv]: {}", message)); 65 | })), 66 | manual: Some(true), 67 | ..Default::default() 68 | }, 69 | ) 70 | }; 71 | let onclick2 = { 72 | let ws2 = ws2.clone(); 73 | let history = history.clone(); 74 | Callback::from(move |_| { 75 | let message = "Hello, yew_hooks!".to_string(); 76 | ws2.send(message.clone()); 77 | history.push(format!("ws2 [send]: {}", message)); 78 | }) 79 | }; 80 | let onopen = { 81 | let ws2 = ws2.clone(); 82 | Callback::from(move |_| { 83 | ws2.open(); 84 | }) 85 | }; 86 | 87 | html! { 88 |
89 |
90 |
91 |

92 | 93 | 94 |

95 |

96 | 97 | 98 |

99 |

100 | { "Message history: " } 101 |

102 | { 103 | for history.current().iter().map(|message| { 104 | html! { 105 |

{ message }

106 | } 107 | }) 108 | } 109 |
110 |
111 |
112 | } 113 | } 114 | -------------------------------------------------------------------------------- /examples/yew-app/src/app/hooks/use_window_scroll.rs: -------------------------------------------------------------------------------- 1 | use yew::prelude::*; 2 | use yew_hooks::prelude::*; 3 | 4 | /// `use_window_scroll` demo 5 | #[function_component] 6 | pub fn UseWindowScroll() -> Html { 7 | let state = use_window_scroll(); 8 | 9 | html! { 10 |
11 |
12 |
13 |

14 | { " X: " } 15 | { state.0 } 16 | { " Y: " } 17 | { state.1 } 18 |

19 |

20 | { "Try to scroll this page vertically or horizontally." } 21 |

22 |
23 |
24 |
25 | } 26 | } 27 | -------------------------------------------------------------------------------- /examples/yew-app/src/app/hooks/use_window_size.rs: -------------------------------------------------------------------------------- 1 | use yew::prelude::*; 2 | use yew_hooks::prelude::*; 3 | 4 | /// `use_window_size` demo 5 | #[function_component] 6 | pub fn UseWindowSize() -> Html { 7 | let state = use_window_size(); 8 | 9 | html! { 10 |
11 |
12 |
13 |

14 | { " Width: " } 15 | { state.0 } 16 | { " Height: " } 17 | { state.1 } 18 |

19 |

20 | { "Try to resize the window of your browser." } 21 |

22 |
23 |
24 |
25 | } 26 | } 27 | -------------------------------------------------------------------------------- /examples/yew-app/src/components/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod nav; 2 | pub mod ui; 3 | -------------------------------------------------------------------------------- /examples/yew-app/src/components/nav.rs: -------------------------------------------------------------------------------- 1 | use yew::prelude::*; 2 | use yew_router::prelude::*; 3 | 4 | use crate::app::AppRoute; 5 | 6 | /// Nav component 7 | #[function_component(Nav)] 8 | pub fn nav() -> Html { 9 | html! { 10 |
11 | 20 |
21 | } 22 | } 23 | -------------------------------------------------------------------------------- /examples/yew-app/src/components/ui/button.rs: -------------------------------------------------------------------------------- 1 | use yew::prelude::*; 2 | 3 | #[derive(Properties, Clone, PartialEq)] 4 | pub struct ButtonProps { 5 | #[prop_or_default] 6 | pub onclick: Callback, 7 | #[prop_or_default] 8 | pub disabled: bool, 9 | #[prop_or_default] 10 | pub button_ref: NodeRef, 11 | #[prop_or_default] 12 | pub children: Children, 13 | } 14 | 15 | #[function_component] 16 | pub fn Button(props: &ButtonProps) -> Html { 17 | html! { 18 | 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /examples/yew-app/src/components/ui/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod button; 2 | -------------------------------------------------------------------------------- /examples/yew-app/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod app; 2 | pub mod components; 3 | -------------------------------------------------------------------------------- /examples/yew-app/src/main.rs: -------------------------------------------------------------------------------- 1 | use yew_app::app::App; 2 | 3 | // This is the entry point for the web app 4 | fn main() { 5 | wasm_logger::init(wasm_logger::Config::default()); 6 | yew::Renderer::::new().render(); 7 | } 8 | -------------------------------------------------------------------------------- /examples/yew-app/tailwind.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | mode: "jit", 3 | content: { 4 | files: ["src/**/*.rs", "**/*.html"], 5 | }, 6 | darkMode: "class", // 'media' or 'class' 7 | theme: { 8 | container: { 9 | center: true, 10 | padding: '2rem', 11 | screens: { 12 | '2xl': '1400px' 13 | } 14 | }, 15 | extend: {}, 16 | }, 17 | variants: { 18 | extend: {}, 19 | }, 20 | plugins: [], 21 | }; -------------------------------------------------------------------------------- /examples/yew-app/tailwind.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; -------------------------------------------------------------------------------- /examples/yew-app/tests/lib.rs: -------------------------------------------------------------------------------- 1 | use std::time::Duration; 2 | use wasm_bindgen_test::*; 3 | use yew::platform::time::sleep; 4 | 5 | wasm_bindgen_test_configure!(run_in_browser); 6 | 7 | use yew_app::app::App; 8 | 9 | #[wasm_bindgen_test] 10 | async fn app_has_a_home_page() { 11 | yew::Renderer::::with_root(gloo::utils::document().get_element_by_id("output").unwrap()) 12 | .render(); 13 | 14 | sleep(Duration::ZERO).await; 15 | 16 | let learn_yew = gloo::utils::document() 17 | .get_element_by_id("yew_hooks") 18 | .expect("No Yew Hooks or no home page") 19 | .inner_html(); 20 | assert_eq!(learn_yew, "Yew Hooks"); 21 | } 22 | --------------------------------------------------------------------------------