yew-hooks 0.4.3

Hooks for the Yew web framework, inspired by react hook libs like streamich/react-use and alibaba/hooks.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
<!-- markdownlint-disable MD033 -->

<h1 align="center">Yew Hooks</h1>

<div align="center">
    <!-- Version -->
    <a href="https://crates.io/crates/yew-hooks">
        <img src="https://img.shields.io/crates/v/yew-hooks.svg"
            alt="crates.io Version" />
    </a>
    <!-- Downloads -->
    <a href="https://crates.io/crates/yew-hooks">
        <img src="https://img.shields.io/crates/d/yew-hooks.svg"
            alt="crates.io Downloads" />
    </a>
    <!-- Docs -->
    <a href="https://docs.rs/yew-hooks">
        <img src="https://img.shields.io/badge/docs-latest-blue.svg"
            alt="docs.rs Docs" />
    </a>
    <!-- CI -->
    <a href="https://github.com/jetli/yew-hooks/actions">
        <img src="https://github.com/jetli/yew-hooks/actions/workflows/rust.yml/badge.svg"
            alt="Github actions CI status" />
    </a>
</div>

<div align="center">
    <h3>
        <a href="https://jetli.github.io/yew-hooks/"> Demos </a>
        <span> | </span>
        <a href="https://github.com/jetli/yew-hooks/tree/main/examples/yew-app"> Examples </a>
        <span> | </span>
        <a href="https://docs.rs/yew-hooks"> Docs </a>
    </h3>
</div>

<br/>

Hooks 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).

```rust
use yew_hooks::prelude::*;

#[function_component(Counter)]
fn counter() -> Html {
    let counter = use_counter(0);

    let onincrease = {
        let counter = counter.clone();
        Callback::from(move |_| counter.increase())
    };
    let ondecrease = {
        let counter = counter.clone();
        Callback::from(move |_| counter.decrease())
    };

    html! {
        <>
            <button onclick={onincrease}>{ "Increase" }</button>
            <button onclick={ondecrease}>{ "Decrease" }</button>
            <b>{ "Current value: " }</b>
            { *counter }
        </>
    }
}
```

## Hooks

### State

- `use_toggle` - tracks state of counterparts.
- `use_bool_toggle` - tracks state of a boolean.
- `use_counter` - tracks state of a number.
- `use_latest` - returns the latest immutable ref to state or props.
- `use_mut_latest` - returns the latest mutable ref to state or props.
- `use_previous` - returns the previous immutable ref to state or props.
- `use_list` - tracks state of a list.
- `use_map` - tracks state of a hash map.
- `use_set` - tracks state of a hash set.
- `use_queue` - tracks state of a queue.
- `use_raf_state` - creates `set` method which only updates after `requestAnimationFrame`.
- `use_state_ptr_eq` - similar to `use_state_eq`, but checks if the two `Rc`s of values point to the same allocation.
- `use_renders_count` - counts component renders.
- `use_default` - returns the default value when state is None.
- `use_debounce_state` - debounces state.
- `use_throttle_state` - throttles state.
- `use_virtual_list` - provides virtual scrolling for large lists to improve performance.

### Side-effects

- `use_async` - resolves an `async` future, e.g. fetching REST api.
- `use_websocket` - communicates with `WebSocket`.
- `use_title` - sets title of the page.
- `use_favicon` - sets favicon of the page.
- `use_local_storage` - manages a value in `localStorage`.
- `use_session_storage` - manages a value in `sessionStorage`.
- `use_before_unload` - shows browser alert when user try to reload or close the page.
- `use_debounce` - debounces a function.
- `use_debounce_effect` - debounces an effect.
- `use_throttle` - throttles a function.
- `use_throttle_effect` - throttles an effect.
- `use_clipboard` - reads from or writes to clipboard for text/bytes.

### Lifecycles

- `use_effect_once` - a modified use_effect hook that only runs once.
- `use_effect_update` - runs an effect only on updates.
- `use_mount` - calls mount callbacks.
- `use_unmount` - calls unmount callbacks.
- `use_is_first_mount` - checks if current render is first.
- `use_is_mounted` - tracks if component is mounted.
- `use_event` - subscribes to events.
- `use_logger` - logs in console as component goes through life cycles.

### Animations

- `use_timeout` - schedules a timeout to invoke callback.
- `use_interval` - schedules an interval to invoke callback.
- `use_update` - returns a callback, which re-renders component when called.
- `use_raf` - re-renders component on each `requestAnimationFrame`.

### Sensors

- `use_window_size` - tracks Window dimensions.
- `use_window_scroll` - tracks Window scroll position.
- `use_scroll` - tracks an HTML element's scroll position.
- `use_scrolling` - tracks whether HTML element is scrolling.
- `use_infinite_scroll` - infinite scrolling of the element.
- `use_location` - tracks brower's location value.
- `use_hash` - tracks brower's location hash value.
- `use_search_param` - tracks brower's location search param value.
- `use_size` - tracks an HTML element's dimensions using the `ResizeObserver` API.
- `use_measure` - tracks an HTML element's dimensions using the `ResizeObserver` API.
- `use_geolocation` - tracks user's geographic location.
- `use_swipe` - detects swipe based on TouchEvent.
- `use_visible` - checks if an element is visible.
- `use_hovered` - checks if an element is hovered.
- `use_permission` - tracks browser's permission changes using the `Permissions` API.

### UI

- `use_click_away` - triggers a callback when user clicks outside the target element.
- `use_drag` - tracks file, link and copy-paste drags, used along with `use_drop` hook.
- `use_drop` - tracks file, link and copy-paste drops.
- `use_media` - plays video or audio and exposes its controls.
- `use_theme` - toggles light/dark theme and persists preference (follows system preference when no explicit choice is stored).

## Examples

### `use_counter` demo

```rust
use yew::prelude::*;
use yew_hooks::prelude::*;

#[function_component(Counter)]
fn counter() -> Html {
    let counter = use_counter(0);

    let onincrease = {
        let counter = counter.clone();
        Callback::from(move |_| counter.increase())
    };
    let ondecrease = {
        let counter = counter.clone();
        Callback::from(move |_| counter.decrease())
    };
    let onincreaseby = {
        let counter = counter.clone();
        Callback::from(move |_| counter.increase_by(10))
    };
    let ondecreaseby = {
        let counter = counter.clone();
        Callback::from(move |_| counter.decrease_by(10))
    };
    let onset = {
        let counter = counter.clone();
        Callback::from(move |_| counter.set(100))
    };
    let onreset = {
        let counter = counter.clone();
        Callback::from(move |_| counter.reset())
    };

    html! {
        <div>
            <button onclick={onincrease}>{ "Increase" }</button>
            <button onclick={ondecrease}>{ "Decrease" }</button>
            <button onclick={onincreaseby}>{ "Increase by 10" }</button>
            <button onclick={ondecreaseby}>{ "Decrease by 10" }</button>
            <button onclick={onset}>{ "Set to 100" }</button>
            <button onclick={onreset}>{ "Reset" }</button>
            <p>
                <b>{ "Current value: " }</b>
                { *counter }
            </p>
        </div>
    }
}
```

### `use_async` demo

```rust
use serde::{de::DeserializeOwned, Deserialize, Serialize};
use yew::prelude::*;
use yew_hooks::prelude::*;

#[function_component(UseAsync)]
pub fn async_demo() -> Html {
    let state = use_async(async move { fetch_repo("jetli/yew-hooks".to_string()).await });

    let onclick = {
        let state = state.clone();
        Callback::from(move |_| {
            // You can trigger to run in callback or use_effect.
            state.run();
        })
    };

    html! {
        <div>
            <button {onclick} disabled={state.loading}>{ "Start to load repo: jetli/yew-hooks" }</button>
            <p>
                {
                    if state.loading {
                        html! { "Loading, wait a sec..." }
                    } else {
                        html! {}
                    }
                }
            </p>
            {
                if let Some(repo) = &state.data {
                    html! {
                        <>
                            <p>{ "Repo name: " }<b>{ &repo.name }</b></p>
                            <p>{ "Repo full name: " }<b>{ &repo.full_name }</b></p>
                            <p>{ "Repo description: " }<b>{ &repo.description }</b></p>

                            <p>{ "Owner name: " }<b>{ &repo.owner.login }</b></p>
                            <p>{ "Owner avatar: " }<b><br/><img alt="avatar" src={repo.owner.avatar_url.clone()} /></b></p>
                        </>
                        }
                } else {
                    html! {}
                }
            }
            <p>
                {
                    if let Some(error) = &state.error {
                        match error {
                            Error::DeserializeError => html! { "DeserializeError" },
                            Error::RequestError => html! { "RequestError" },
                        }
                    } else {
                        html! {}
                    }
                }
            </p>
        </div>
    }
}

async fn fetch_repo(repo: String) -> Result<Repo, Error> {
    fetch::<Repo>(format!("https://api.github.com/repos/{}", repo)).await
}

/// You can use reqwest or other crates to fetch your api.
async fn fetch<T>(url: String) -> Result<T, Error>
where
    T: DeserializeOwned,
{
    let response = reqwest::get(url).await;
    if let Ok(data) = response {
        if let Ok(repo) = data.json::<T>().await {
            Ok(repo)
        } else {
            Err(Error::DeserializeError)
        }
    } else {
        Err(Error::RequestError)
    }
}

#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
struct User {
    id: i32,
    login: String,
    avatar_url: String,
}

#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
struct Repo {
    id: i32,
    name: String,
    full_name: String,
    description: String,
    owner: User,
}

// You can use thiserror to define your errors.
#[derive(Clone, Debug, PartialEq)]
enum Error {
    RequestError,
    DeserializeError,
    // etc.
}
```

### `use_websocket` demo

```rust
use yew::prelude::*;
use yew_hooks::prelude::*;

#[function_component(UseWebSocket)]
pub fn web_socket() -> Html {
    let history = use_list(vec![]);
    let ws = use_websocket("wss://echo.websocket.events/".to_string());

    let onclick = {
        let ws = ws.clone();
        let history = history.clone();
        Callback::from(move |_| {
            let message = "Hello, world!".to_string();
            ws.send(message.clone());
            history.push(format!("[send]: {}", message));
        })
    };

    {
        let history = history.clone();
        let ws = ws.clone();
        use_effect_with(
            ws.message,
            move |message| {
                if let Some(message) = &**message {
                    history.push(format!("[recv]: {}", message.clone()));
                }
                || ()
            },
        );
    }

    html! {
        <div>
            <p>
                <button {onclick} disabled={*ws.ready_state != UseWebSocketReadyState::Open}>{ "Send" }</button>
            </p>
            <p>
                <b>{ "Message history: " }</b>
            </p>
            {
                for history.current().iter().map(|message| {
                    html! {
                        <p>{ message }</p>
                    }
                })
            }
        </div>
    }
}
```

### `use_theme` demo

A 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.

```rust
use yew::prelude::*;
use yew_hooks::prelude::*;

#[function_component(ThemeDemo)]
fn theme_demo() -> Html {
    // Use a storage key for persistence across reloads/tabs
    let theme = use_theme("example_theme".to_string());

    let onclick = {
        let theme = theme.clone();
        Callback::from(move |_| theme.toggle())
    };

    html! {
        <>
            <button {onclick}>{ if theme.is_dark() { "Switch to light" } else { "Switch to dark" } }</button>
            <p>{ format!("Active theme: {}", *theme) }</p>
        </>
    }
}
```

## Demo

[Check out a live demo](https://jetli.github.io/yew-hooks/)

## Contribute

Feel free to take a look at the current issues in this repo for anything that currently needs to be worked on.

You are also welcome to open a PR or a new issue if you see something is missing or could be improved upon.

## License

Apache-2.0/MIT