diff --git a/crates/yewdux-input/src/lib.rs b/crates/yewdux-input/src/lib.rs index ceec102..2d8e134 100644 --- a/crates/yewdux-input/src/lib.rs +++ b/crates/yewdux-input/src/lib.rs @@ -1,10 +1,10 @@ use std::{rc::Rc, str::FromStr}; +use serde::{Deserialize, Serialize}; use wasm_bindgen::JsCast; use web_sys::{HtmlInputElement, HtmlTextAreaElement}; use yew::prelude::*; use yewdux::prelude::*; -use serde::{Deserialize, Serialize}; pub enum InputElement { Input(HtmlInputElement), diff --git a/crates/yewdux-macros/src/store.rs b/crates/yewdux-macros/src/store.rs index cefd878..332b196 100644 --- a/crates/yewdux-macros/src/store.rs +++ b/crates/yewdux-macros/src/store.rs @@ -1,4 +1,4 @@ -use darling::FromDeriveInput; +use darling::{util::PathList, FromDeriveInput}; use proc_macro2::TokenStream; use quote::quote; use syn::DeriveInput; @@ -8,6 +8,7 @@ use syn::DeriveInput; struct Opts { storage: Option, storage_tab_sync: bool, + listener: PathList, } pub(crate) fn derive(input: DeriveInput) -> TokenStream { @@ -15,6 +16,18 @@ pub(crate) fn derive(input: DeriveInput) -> TokenStream { let ident = input.ident; let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl(); + let extra_listeners: Vec<_> = opts + .listener + .iter() + .map(|path| { + quote! { + ::yewdux::listener::init_listener( + #path + ); + } + }) + .collect(); + let impl_ = match opts.storage { Some(storage) => { let area = match storage.as_ref() { @@ -42,6 +55,7 @@ pub(crate) fn derive(input: DeriveInput) -> TokenStream { ::yewdux::listener::init_listener( ::yewdux::storage::StorageListener::::new(#area) ); + #(#extra_listeners)* #sync @@ -58,12 +72,14 @@ pub(crate) fn derive(input: DeriveInput) -> TokenStream { #[cfg(not(target_arch = "wasm32"))] fn new() -> Self { + #(#extra_listeners)* Default::default() } } } None => quote! { fn new() -> Self { + #(#extra_listeners)* Default::default() } }, diff --git a/crates/yewdux/src/listener.rs b/crates/yewdux/src/listener.rs index 8bbc082..c3c65ac 100644 --- a/crates/yewdux/src/listener.rs +++ b/crates/yewdux/src/listener.rs @@ -9,8 +9,8 @@ pub trait Listener: 'static { fn on_change(&mut self, state: Rc); } -struct ListenerStore(Option>); -impl Store for Mrc> { +struct ListenerStore(Option>); +impl Store for Mrc> { fn new() -> Self { ListenerStore(None).into() } @@ -28,9 +28,7 @@ pub fn init_listener(listener: L) { dispatch::subscribe_silent(move |state| listener.borrow_mut().on_change(state)) }; - dispatch::reduce_mut(|state: &mut Mrc>| { - state.borrow_mut().0 = Some(id) - }); + dispatch::reduce_mut(|state: &mut Mrc>| state.borrow_mut().0 = Some(id)); } #[cfg(test)] @@ -62,6 +60,16 @@ mod tests { } } + #[derive(Clone)] + struct AnotherTestListener(Rc>); + impl Listener for AnotherTestListener { + type Store = TestState; + + fn on_change(&mut self, state: Rc) { + self.0.set(state.0); + } + } + #[derive(Clone, PartialEq, Eq)] struct TestState2; impl Store for TestState2 { @@ -113,6 +121,25 @@ mod tests { assert_eq!(listener2.0.get(), 2); } + #[test] + fn listener_of_different_type_is_not_replaced() { + let listener1 = TestListener(Default::default()); + let listener2 = AnotherTestListener(Default::default()); + + init_listener(listener1.clone()); + + dispatch::reduce_mut(|state: &mut TestState| state.0 = 1); + + assert_eq!(listener1.0.get(), 1); + + init_listener(listener2.clone()); + + dispatch::reduce_mut(|state: &mut TestState| state.0 = 2); + + assert_eq!(listener1.0.get(), 2); + assert_eq!(listener2.0.get(), 2); + } + #[test] fn can_init_listener_from_store() { dispatch::get::(); diff --git a/docs/src/persistence.md b/docs/src/persistence.md index 23a687c..72fd985 100644 --- a/docs/src/persistence.md +++ b/docs/src/persistence.md @@ -46,3 +46,24 @@ struct State { } ``` +## Additional Listeners + +You can inject additional listeners into the `#[store]` macro. + +```rust +#[derive(Default, Clone, PartialEq, Eq, Deserialize, Serialize, Store)] +#[store(storage = "local", listener(LogListener))] +struct State { + count: u32, +} + +struct LogListener; +impl Listener for LogListener { + type Store = State; + + fn on_change(&mut self, state: Rc) { + log!(Level::Info, "Count changed to {}", state.count); + } +} +``` + diff --git a/examples/listener/Cargo.toml b/examples/listener/Cargo.toml index cc452c2..702c897 100644 --- a/examples/listener/Cargo.toml +++ b/examples/listener/Cargo.toml @@ -7,5 +7,6 @@ edition = "2018" [dependencies] serde = { version = "1.0.114" } +wasm-logger = "0.2.0" yew = { git = "https://github.com/yewstack/yew.git", features = ["csr"] } yewdux = { path = "../../crates/yewdux" } diff --git a/examples/listener/src/main.rs b/examples/listener/src/main.rs index ac0df82..eee235a 100644 --- a/examples/listener/src/main.rs +++ b/examples/listener/src/main.rs @@ -1,12 +1,24 @@ use std::rc::Rc; use yew::prelude::*; -use yewdux::prelude::*; #[cfg(target_arch = "wasm32")] use yewdux::storage; +use yewdux::{ + log::{log, Level}, + prelude::*, +}; use serde::{Deserialize, Serialize}; +struct LogListener; +impl Listener for LogListener { + type Store = State; + + fn on_change(&mut self, state: Rc) { + log!(Level::Info, "Count changed to {}", state.count); + } +} + struct StorageListener; impl Listener for StorageListener { type Store = State; @@ -33,6 +45,7 @@ impl Store for State { #[cfg(target_arch = "wasm32")] fn new() -> Self { init_listener(StorageListener); + init_listener(LogListener); storage::load(storage::Area::Local) .ok() @@ -59,5 +72,6 @@ fn App() -> Html { } fn main() { + wasm_logger::init(wasm_logger::Config::default()); yew::Renderer::::new().render(); } diff --git a/examples/persistent/Cargo.toml b/examples/persistent/Cargo.toml index babe837..29efacd 100644 --- a/examples/persistent/Cargo.toml +++ b/examples/persistent/Cargo.toml @@ -7,5 +7,6 @@ edition = "2018" [dependencies] serde = { version = "1.0.114" } +wasm-logger = "0.2.0" yew = { git = "https://github.com/yewstack/yew.git", features = ["csr"] } yewdux = { path = "../../crates/yewdux" } diff --git a/examples/persistent/src/main.rs b/examples/persistent/src/main.rs index 67b3fe8..be7c227 100644 --- a/examples/persistent/src/main.rs +++ b/examples/persistent/src/main.rs @@ -1,10 +1,15 @@ +use std::rc::Rc; + use yew::prelude::*; -use yewdux::prelude::*; +use yewdux::{ + log::{log, Level}, + prelude::*, +}; use serde::{Deserialize, Serialize}; #[derive(Default, Clone, PartialEq, Eq, Deserialize, Serialize, Store)] -#[store(storage = "local")] +#[store(storage = "local", listener(LogListener))] struct State { count: u32, } @@ -23,5 +28,15 @@ fn App() -> Html { } fn main() { + wasm_logger::init(wasm_logger::Config::default()); yew::Renderer::::new().render(); } + +struct LogListener; +impl Listener for LogListener { + type Store = State; + + fn on_change(&mut self, state: Rc) { + log!(Level::Info, "Count changed to {}", state.count); + } +} diff --git a/examples/todomvc/src/state.rs b/examples/todomvc/src/state.rs index 72251eb..6e02dfe 100644 --- a/examples/todomvc/src/state.rs +++ b/examples/todomvc/src/state.rs @@ -119,8 +119,7 @@ pub struct Entry { pub editing: bool, } -#[derive(Clone, Copy, Debug, EnumIter, Display, PartialEq, Eq, Serialize, Deserialize)] -#[derive(Default)] +#[derive(Clone, Copy, Debug, EnumIter, Display, PartialEq, Eq, Serialize, Deserialize, Default)] pub enum Filter { #[default] All, @@ -145,5 +144,3 @@ impl Filter { } } } - -