{"id":13807328,"url":"https://github.com/jetli/yew-hooks","last_synced_at":"2026-04-01T18:05:43.788Z","repository":{"id":37245207,"uuid":"441040825","full_name":"jetli/yew-hooks","owner":"jetli","description":"Hooks for Yew, inspired by streamich/react-use and alibaba/hooks.","archived":false,"fork":false,"pushed_at":"2026-03-30T10:35:45.000Z","size":58473,"stargazers_count":177,"open_issues_count":7,"forks_count":16,"subscribers_count":2,"default_branch":"main","last_synced_at":"2026-03-30T12:22:58.911Z","etag":null,"topics":["hooks","react","react-hooks","rust","wasm","webassembly","yew","yew-hooks"],"latest_commit_sha":null,"homepage":"https://jetli.github.io/yew-hooks/","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jetli.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE-APACHE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2021-12-23T02:35:18.000Z","updated_at":"2026-03-30T10:32:18.000Z","dependencies_parsed_at":"2024-03-05T03:29:50.113Z","dependency_job_id":"cccf7d63-ae3f-4937-8424-964489ca7620","html_url":"https://github.com/jetli/yew-hooks","commit_stats":{"total_commits":117,"total_committers":3,"mean_commits":39.0,"dds":"0.042735042735042694","last_synced_commit":"9285a3a5c316f57b84c736713b3a54e70857e24d"},"previous_names":[],"tags_count":29,"template":false,"template_full_name":null,"purl":"pkg:github/jetli/yew-hooks","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jetli%2Fyew-hooks","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jetli%2Fyew-hooks/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jetli%2Fyew-hooks/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jetli%2Fyew-hooks/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jetli","download_url":"https://codeload.github.com/jetli/yew-hooks/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jetli%2Fyew-hooks/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31290747,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-01T13:12:26.723Z","status":"ssl_error","status_checked_at":"2026-04-01T13:12:25.102Z","response_time":53,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["hooks","react","react-hooks","rust","wasm","webassembly","yew","yew-hooks"],"created_at":"2024-08-04T01:01:24.212Z","updated_at":"2026-04-01T18:05:43.766Z","avatar_url":"https://github.com/jetli.png","language":"Rust","funding_links":[],"categories":["Rust","Crates"],"sub_categories":["Hooks"],"readme":"\u003c!-- markdownlint-disable MD033 --\u003e\n\n\u003ch1 align=\"center\"\u003eYew Hooks\u003c/h1\u003e\n\n\u003cdiv align=\"center\"\u003e\n    \u003c!-- Version --\u003e\n    \u003ca href=\"https://crates.io/crates/yew-hooks\"\u003e\n        \u003cimg src=\"https://img.shields.io/crates/v/yew-hooks.svg\"\n            alt=\"crates.io Version\" /\u003e\n    \u003c/a\u003e\n    \u003c!-- Downloads --\u003e\n    \u003ca href=\"https://crates.io/crates/yew-hooks\"\u003e\n        \u003cimg src=\"https://img.shields.io/crates/d/yew-hooks.svg\"\n            alt=\"crates.io Downloads\" /\u003e\n    \u003c/a\u003e\n    \u003c!-- Docs --\u003e\n    \u003ca href=\"https://docs.rs/yew-hooks\"\u003e\n        \u003cimg src=\"https://img.shields.io/badge/docs-latest-blue.svg\"\n            alt=\"docs.rs Docs\" /\u003e\n    \u003c/a\u003e\n    \u003c!-- CI --\u003e\n    \u003ca href=\"https://github.com/jetli/yew-hooks/actions\"\u003e\n        \u003cimg src=\"https://github.com/jetli/yew-hooks/actions/workflows/rust.yml/badge.svg\"\n            alt=\"Github actions CI status\" /\u003e\n    \u003c/a\u003e\n\u003c/div\u003e\n\n\u003cdiv align=\"center\"\u003e\n    \u003ch3\u003e\n        \u003ca href=\"https://jetli.github.io/yew-hooks/\"\u003e Demos \u003c/a\u003e\n        \u003cspan\u003e | \u003c/span\u003e\n        \u003ca href=\"https://github.com/jetli/yew-hooks/tree/main/examples/yew-app\"\u003e Examples \u003c/a\u003e\n        \u003cspan\u003e | \u003c/span\u003e\n        \u003ca href=\"https://docs.rs/yew-hooks\"\u003e Docs \u003c/a\u003e\n    \u003c/h3\u003e\n\u003c/div\u003e\n\n\u003cbr/\u003e\n\nHooks for [Yew](https://github.com/yewstack/yew), inspired by [streamich/react-use](https://github.com/streamich/react-use), [alibaba/hooks](https://github.com/alibaba/hooks) and [vueuse/vueuse](https://github.com/vueuse/vueuse).\n\n```rust\nuse yew_hooks::prelude::*;\n\n#[function_component(Counter)]\nfn counter() -\u003e Html {\n    let counter = use_counter(0);\n\n    let onincrease = {\n        let counter = counter.clone();\n        Callback::from(move |_| counter.increase())\n    };\n    let ondecrease = {\n        let counter = counter.clone();\n        Callback::from(move |_| counter.decrease())\n    };\n\n    html! {\n        \u003c\u003e\n            \u003cbutton onclick={onincrease}\u003e{ \"Increase\" }\u003c/button\u003e\n            \u003cbutton onclick={ondecrease}\u003e{ \"Decrease\" }\u003c/button\u003e\n            \u003cb\u003e{ \"Current value: \" }\u003c/b\u003e\n            { *counter }\n        \u003c/\u003e\n    }\n}\n```\n\n## Hooks\n\n### State\n\n- `use_toggle` - tracks state of counterparts.\n- `use_bool_toggle` - tracks state of a boolean.\n- `use_counter` - tracks state of a number.\n- `use_latest` - returns the latest immutable ref to state or props.\n- `use_mut_latest` - returns the latest mutable ref to state or props.\n- `use_previous` - returns the previous immutable ref to state or props.\n- `use_list` - tracks state of a list.\n- `use_map` - tracks state of a hash map.\n- `use_set` - tracks state of a hash set.\n- `use_queue` - tracks state of a queue.\n- `use_raf_state` - creates `set` method which only updates after `requestAnimationFrame`.\n- `use_state_ptr_eq` - similar to `use_state_eq`, but checks if the two `Rc`s of values point to the same allocation.\n- `use_renders_count` - counts component renders.\n- `use_default` - returns the default value when state is None.\n- `use_debounce_state` - debounces state.\n- `use_throttle_state` - throttles state.\n- `use_virtual_list` - provides virtual scrolling for large lists to improve performance.\n\n### Side-effects\n\n- `use_async` - resolves an `async` future, e.g. fetching REST api.\n- `use_websocket` - communicates with `WebSocket`.\n- `use_title` - sets title of the page.\n- `use_favicon` - sets favicon of the page.\n- `use_local_storage` - manages a value in `localStorage`.\n- `use_session_storage` - manages a value in `sessionStorage`.\n- `use_cookie` - manages browser cookies.\n- `use_before_unload` - shows browser alert when user try to reload or close the page.\n- `use_debounce` - debounces a function.\n- `use_debounce_effect` - debounces an effect.\n- `use_throttle` - throttles a function.\n- `use_throttle_effect` - throttles an effect.\n- `use_clipboard` - reads from or writes to clipboard for text/bytes.\n\n### Lifecycles\n\n- `use_effect_once` - a modified use_effect hook that only runs once.\n- `use_effect_update` - runs an effect only on updates.\n- `use_mount` - calls mount callbacks.\n- `use_unmount` - calls unmount callbacks.\n- `use_is_first_mount` - checks if current render is first.\n- `use_is_mounted` - tracks if component is mounted.\n- `use_event` - subscribes to events.\n- `use_logger` - logs in console as component goes through life cycles.\n\n### Animations\n\n- `use_timeout` - schedules a timeout to invoke callback.\n- `use_interval` - schedules an interval to invoke callback.\n- `use_update` - returns a callback, which re-renders component when called.\n- `use_raf` - re-renders component on each `requestAnimationFrame`.\n\n### Sensors\n\n- `use_window_size` - tracks Window dimensions.\n- `use_window_scroll` - tracks Window scroll position.\n- `use_scroll` - tracks an HTML element's scroll position.\n- `use_scrolling` - tracks whether HTML element is scrolling.\n- `use_infinite_scroll` - infinite scrolling of the element.\n- `use_location` - tracks browser's location value.\n- `use_hash` - tracks browser's location hash value.\n- `use_search_param` - tracks browser's location search param value.\n- `use_size` - tracks an HTML element's dimensions using the `ResizeObserver` API.\n- `use_measure` - tracks an HTML element's dimensions using the `ResizeObserver` API.\n- `use_geolocation` - tracks user's geographic location.\n- `use_swipe` - detects swipe based on TouchEvent.\n- `use_long_press` - detects when a user presses and holds an element for a specified duration.\n- `use_visible` - checks if an element is visible.\n- `use_hovered` - checks if an element is hovered.\n- `use_permission` - tracks browser's permission changes using the `Permissions` API.\n- `use_idle` - tracks whether the user is idle (not interacting with the page).\n\n### UI\n\n- `use_click_away` - triggers a callback when user clicks outside the target element.\n- `use_drag` - tracks file, link and copy-paste drags, used along with `use_drop` hook.\n- `use_drop` - tracks file, link and copy-paste drops.\n- `use_media` - plays video or audio and exposes its controls.\n- `use_fullscreen` - controls fullscreen mode for elements.\n- `use_theme` - toggles light/dark theme and persists preference (follows system preference when no explicit choice is stored).\n\n## Examples\n\n### `use_counter` demo\n\n```rust\nuse yew::prelude::*;\nuse yew_hooks::prelude::*;\n\n#[function_component(Counter)]\nfn counter() -\u003e Html {\n    let counter = use_counter(0);\n\n    let onincrease = {\n        let counter = counter.clone();\n        Callback::from(move |_| counter.increase())\n    };\n    let ondecrease = {\n        let counter = counter.clone();\n        Callback::from(move |_| counter.decrease())\n    };\n    let onincreaseby = {\n        let counter = counter.clone();\n        Callback::from(move |_| counter.increase_by(10))\n    };\n    let ondecreaseby = {\n        let counter = counter.clone();\n        Callback::from(move |_| counter.decrease_by(10))\n    };\n    let onset = {\n        let counter = counter.clone();\n        Callback::from(move |_| counter.set(100))\n    };\n    let onreset = {\n        let counter = counter.clone();\n        Callback::from(move |_| counter.reset())\n    };\n\n    html! {\n        \u003cdiv\u003e\n            \u003cbutton onclick={onincrease}\u003e{ \"Increase\" }\u003c/button\u003e\n            \u003cbutton onclick={ondecrease}\u003e{ \"Decrease\" }\u003c/button\u003e\n            \u003cbutton onclick={onincreaseby}\u003e{ \"Increase by 10\" }\u003c/button\u003e\n            \u003cbutton onclick={ondecreaseby}\u003e{ \"Decrease by 10\" }\u003c/button\u003e\n            \u003cbutton onclick={onset}\u003e{ \"Set to 100\" }\u003c/button\u003e\n            \u003cbutton onclick={onreset}\u003e{ \"Reset\" }\u003c/button\u003e\n            \u003cp\u003e\n                \u003cb\u003e{ \"Current value: \" }\u003c/b\u003e\n                { *counter }\n            \u003c/p\u003e\n        \u003c/div\u003e\n    }\n}\n```\n\n### `use_async` demo\n\n```rust\nuse serde::{de::DeserializeOwned, Deserialize, Serialize};\nuse yew::prelude::*;\nuse yew_hooks::prelude::*;\n\n#[function_component(UseAsync)]\npub fn async_demo() -\u003e Html {\n    let state = use_async(async move { fetch_repo(\"jetli/yew-hooks\".to_string()).await });\n\n    let onclick = {\n        let state = state.clone();\n        Callback::from(move |_| {\n            // You can trigger to run in callback or use_effect.\n            state.run();\n        })\n    };\n\n    html! {\n        \u003cdiv\u003e\n            \u003cbutton {onclick} disabled={state.loading}\u003e{ \"Start to load repo: jetli/yew-hooks\" }\u003c/button\u003e\n            \u003cp\u003e\n                {\n                    if state.loading {\n                        html! { \"Loading, wait a sec...\" }\n                    } else {\n                        html! {}\n                    }\n                }\n            \u003c/p\u003e\n            {\n                if let Some(repo) = \u0026state.data {\n                    html! {\n                        \u003c\u003e\n                            \u003cp\u003e{ \"Repo name: \" }\u003cb\u003e{ \u0026repo.name }\u003c/b\u003e\u003c/p\u003e\n                            \u003cp\u003e{ \"Repo full name: \" }\u003cb\u003e{ \u0026repo.full_name }\u003c/b\u003e\u003c/p\u003e\n                            \u003cp\u003e{ \"Repo description: \" }\u003cb\u003e{ \u0026repo.description }\u003c/b\u003e\u003c/p\u003e\n\n                            \u003cp\u003e{ \"Owner name: \" }\u003cb\u003e{ \u0026repo.owner.login }\u003c/b\u003e\u003c/p\u003e\n                            \u003cp\u003e{ \"Owner avatar: \" }\u003cb\u003e\u003cbr/\u003e\u003cimg alt=\"avatar\" src={repo.owner.avatar_url.clone()} /\u003e\u003c/b\u003e\u003c/p\u003e\n                        \u003c/\u003e\n                        }\n                } else {\n                    html! {}\n                }\n            }\n            \u003cp\u003e\n                {\n                    if let Some(error) = \u0026state.error {\n                        match error {\n                            Error::DeserializeError =\u003e html! { \"DeserializeError\" },\n                            Error::RequestError =\u003e html! { \"RequestError\" },\n                        }\n                    } else {\n                        html! {}\n                    }\n                }\n            \u003c/p\u003e\n        \u003c/div\u003e\n    }\n}\n\nasync fn fetch_repo(repo: String) -\u003e Result\u003cRepo, Error\u003e {\n    fetch::\u003cRepo\u003e(format!(\"https://api.github.com/repos/{}\", repo)).await\n}\n\n/// You can use reqwest or other crates to fetch your api.\nasync fn fetch\u003cT\u003e(url: String) -\u003e Result\u003cT, Error\u003e\nwhere\n    T: DeserializeOwned,\n{\n    let response = reqwest::get(url).await;\n    if let Ok(data) = response {\n        if let Ok(repo) = data.json::\u003cT\u003e().await {\n            Ok(repo)\n        } else {\n            Err(Error::DeserializeError)\n        }\n    } else {\n        Err(Error::RequestError)\n    }\n}\n\n#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]\nstruct User {\n    id: i32,\n    login: String,\n    avatar_url: String,\n}\n\n#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]\nstruct Repo {\n    id: i32,\n    name: String,\n    full_name: String,\n    description: String,\n    owner: User,\n}\n\n// You can use thiserror to define your errors.\n#[derive(Clone, Debug, PartialEq)]\nenum Error {\n    RequestError,\n    DeserializeError,\n    // etc.\n}\n```\n\n### `use_websocket` demo\n\n```rust\nuse yew::prelude::*;\nuse yew_hooks::prelude::*;\n\n#[function_component(UseWebSocket)]\npub fn web_socket() -\u003e Html {\n    let history = use_list(vec![]);\n    let ws = use_websocket(\"wss://echo.websocket.events/\".to_string());\n\n    let onclick = {\n        let ws = ws.clone();\n        let history = history.clone();\n        Callback::from(move |_| {\n            let message = \"Hello, world!\".to_string();\n            ws.send(message.clone());\n            history.push(format!(\"[send]: {}\", message));\n        })\n    };\n\n    {\n        let history = history.clone();\n        let ws = ws.clone();\n        use_effect_with(\n            ws.message,\n            move |message| {\n                if let Some(message) = \u0026**message {\n                    history.push(format!(\"[recv]: {}\", message.clone()));\n                }\n                || ()\n            },\n        );\n    }\n\n    html! {\n        \u003cdiv\u003e\n            \u003cp\u003e\n                \u003cbutton {onclick} disabled={*ws.ready_state != UseWebSocketReadyState::Open}\u003e{ \"Send\" }\u003c/button\u003e\n            \u003c/p\u003e\n            \u003cp\u003e\n                \u003cb\u003e{ \"Message history: \" }\u003c/b\u003e\n            \u003c/p\u003e\n            {\n                for history.current().iter().map(|message| {\n                    html! {\n                        \u003cp\u003e{ message }\u003c/p\u003e\n                    }\n                })\n            }\n        \u003c/div\u003e\n    }\n}\n```\n\n### `use_theme` demo\n\nA small example demonstrating the `use_theme` hook. The hook persists an explicit user preference to `localStorage` under the provided key, or follows the system `prefers-color-scheme` when no explicit choice exists.\n\n```rust\nuse yew::prelude::*;\nuse yew_hooks::prelude::*;\n\n#[function_component(ThemeDemo)]\nfn theme_demo() -\u003e Html {\n    // Use a storage key for persistence across reloads/tabs\n    let theme = use_theme(\"example_theme\".to_string());\n\n    let onclick = {\n        let theme = theme.clone();\n        Callback::from(move |_| theme.toggle())\n    };\n\n    html! {\n        \u003c\u003e\n            \u003cbutton {onclick}\u003e{ if theme.is_dark() { \"Switch to light\" } else { \"Switch to dark\" } }\u003c/button\u003e\n            \u003cp\u003e{ format!(\"Active theme: {}\", *theme) }\u003c/p\u003e\n        \u003c/\u003e\n    }\n}\n```\n\n## Demo\n\n[Check out a live demo](https://jetli.github.io/yew-hooks/)\n\n## Contribute\n\nFeel free to take a look at the current issues in this repo for anything that currently needs to be worked on.\n\nYou are also welcome to open a PR or a new issue if you see something is missing or could be improved upon.\n\n## License\n\nApache-2.0/MIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjetli%2Fyew-hooks","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjetli%2Fyew-hooks","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjetli%2Fyew-hooks/lists"}