--- title: 'Custom Hooks' --- ## Defining custom Hooks The stateful logic of a component can be extracted into reusable functions by creating custom Hooks. Consider that we wish to create an event listener that listens to an event on the `window` object. ```rust use yew::prelude::*; use gloo::events::EventListener; use gloo::utils::window; use std::mem::drop; #[function_component(ShowStorageChanged)] pub fn show_storage_changed() -> Html { let state_storage_changed = use_state(|| false); { let state_storage_changed = state_storage_changed.clone(); use_effect(|| { let listener = EventListener::new(&window(), "storage", move |_| state_storage_changed.set(true)); move || { drop(listener); } }); } html! {
{"Storage Event Fired: "}{*state_storage_changed}
} } ``` There's one problem with this code: the logic can't be reused by another component. If we build another component that listens to a different event, instead of copying the code, we can move the logic into a custom hook. We'll start by creating a new function called `use_event`. The `use_` prefix denotes that a function is a hook. This function will take an event target, an event type, and a callback. All hooks must be marked by `#[hook]` on their function definition. ```rust use web_sys::{Event, EventTarget}; use std::borrow::Cow; use gloo::events::EventListener; use yew::prelude::*; #[hook] pub fn use_event(target: &EventTarget, event_type: E, callback: F) where E: Into>, F: Fn(&Event) + 'static, { todo!() } ``` This simple hook can be created by composing built-in hooks. For this example, we'll use the `use_effect_with` hook, so an event listener can be recreated when the hook arguments change. ```rust use yew::prelude::*; use web_sys::{Event, EventTarget}; use std::borrow::Cow; use std::rc::Rc; use gloo::events::EventListener; #[hook] pub fn use_event(target: &EventTarget, event_type: E, callback: F) where E: Into>, F: Fn(Event) + 'static, { #[derive(PartialEq, Clone)] struct EventDependents { target: EventTarget, event_type: Cow<'static, str>, callback: Callback, } let deps = EventDependents { target: target.clone(), event_type: event_type.into(), callback: Callback::from(callback), }; use_effect_with( deps, |deps| { let EventDependents { target, event_type, callback, } = deps.clone(); let listener = EventListener::new(&target, event_type, move |e| { callback.emit(e.clone()); }); move || { drop(listener); } }, ); } ``` Although this approach works in almost all cases, it can't be used to write primitive hooks like the pre-defined hooks we've been using already. View the docs on [docs.rs](https://docs.rs/yew) for documentation and `hooks` directory to see implementations of pre-defined hooks.