[−][src]Crate yew_state
This crate provides ergonomic access to shared state via component wrapper, with optional local/session persistence and custom scoping.
Initially this was a PR, but became big enough to warrant a standalone crate.
If you have suggestions please open an issue, or join in on the discussion.
Quickstart
To get started use the SharedStateComponent
wrapper or StateView
component.
SharedStateComponent
Give your component any properties that implement SharedState
then wrap it with
SharedStateComponent
.
IMPORTANT: Changes must be handled in the component's change
method.
use yew::prelude::*; use yew_state::{SharedHandle, SharedStateComponent}; use yewtil::NeqAssign; #[derive(Clone, Default)] pub type AppState { pub count: usize, } pub struct Model { handle: SharedHandle<AppState>, } impl Component for Model { type Message = (); type Properties = SharedHandle<AppState>; fn create(handle: Self::Properties, _link: ComponentLink<Self>) -> Self { handle.reduce(|state| state.count = 1); // Magically set count to one for example Model { handle } } fn update(&mut self, msg: Self::Message) -> ShouldRender { true } fn change(&mut self, handle: Self::Properties) -> ShouldRender { self.handle.neq_assign(handle) } fn view(&self) -> Html { let onclick = self.handle.reduce_callback(|state| state.count += 1); let count = self.handle.state().count; html! { <p>{count}</p> <button onclick=onclick>{"+1"}</button> } } } pub type App = SharedStateComponent<Model>;
StateView
For something simpler, StateView
can handle shared state with less boilerplate.
Keep in mind you can't selectively re-render changes this way.
use yew::prelude::*; use yew_state::{view_state, StateView, SharedHandle}; type CountHandle = SharedHandle<usize>; fn view_counter() -> Html { html! { <> { view_display() } { view_input() } </> } } fn view_display() -> Html { let view = view_state(|handle: &CountHandle| { html! { <p>{handle.state()}</p> } }); html! { <StateView<CountHandle> view=view /> } } fn view_input() -> Html { let view = view_state(|handle: &CountHandle| { let onclick = handle.reduce_callback(|count| *count += 1); html! { <button onclick=onclick>{"+1"}</button> } }); html! { <StateView<CountHandle> view=view /> } }
Handling State
State handles provide an interface to shared state. SharedHandle
for basic access, while
StorageHandle
also does persistent local/session storage.
IMPORTANT: Changes to state do not take effect immediately! New state must be handled in the
component's change
method.
state
provides current state.
let state: &T = self.handle.state();
reduce
can be used from anywhere to modify shared state.
// SharedHandle<MyAppState> self.handle.reduce(move |state| state.user = new_user);
reduce_callback
allows modifying shared state from a callback.
// SharedHandle<usize> let onclick = self.handle.reduce_callback(|state| *state += 1); html! { <button onclick=onclick>{"+1"}</button> }
reduce_callback_with
provides the fired event as well.
let oninput = self .handle .reduce_callback_with(|state, i: InputData| state.user.name = i.value); html! { <input type="text" placeholder="Enter your name" oninput=oninput /> }
Custom Properties
SharedState
can be implemented for any properties.
TODO: This could be a macro.
#[derive(Clone, Properties)] pub struct Props { #[prop_or_default] handle: SharedHandle<AppState>, } impl SharedState for Props { type Handle = SharedHandle<AppState>; fn handle(&mut self) -> &mut Self::Handle { &mut self.handle } }
Persistence
To make state persistent use StorageHandle
instead of SharedHandle
. This requires state to also implement Serialize
,
Deserialize
, and Storable
.
TODO: This could be a macro.
use serde::{Serialize, Deserialize}; use yew_state::{Storable, Area}; #[derive(Clone, Default, Serialize, Deserialize)] struct T; impl Storable for T { fn area() -> Area { Area::Session // Defaults to Area::Local } }
Scoping
Sometimes it's useful to only share state within a specific scope. This may be done by providing a
custom scope to SharedStateComponent
or StateView
:
pub struct MyScope; pub struct MyComponent = SharedStateComponent<MyModel, MyScope>;
Example
This example demonstrates how two counters with different scopes can increment shared state independently.
use yew::prelude::*; use yew_state::{view_state, StateView, SharedHandle}; struct FooScope; struct BarScope; type CountHandle = SharedHandle<usize>; fn view_input<SCOPE: 'static>() -> Html { let view = view_state(|handle: &CountHandle| { let onclick = handle.reduce_callback(|count| *count += 1); html! { <button onclick=onclick>{"+1"}</button> } }); html! { <StateView<CountHandle, SCOPE> view=view /> } } fn view_display<SCOPE: 'static>() -> Html { let view = view_state(|handle: &CountHandle| { html! { <p>{handle.state()}</p> } }); html! { <StateView<CountHandle, SCOPE> view=view /> } } pub struct App; impl Component for App { type Message = (); type Properties = (); fn create(_props: Self::Properties, _link: ComponentLink<Self>) -> Self { Self } fn update(&mut self, msg: Self::Message) -> ShouldRender { true } fn change(&mut self, props: Self::Properties) -> ShouldRender { true } fn view(&self) -> Html { html! { <> <h1>{"FooScope"}</h1> { view_display::<FooScope>() } { view_input::<FooScope>() } <h1>{"BarScope"}</h1> { view_display::<BarScope>() } { view_input::<BarScope>() } </> } } }
This crate provides ergonomic access to shared state via component wrapper, with optional local/session persistence and custom scoping.
Initially this was a PR, but became big enough to warrant a standalone crate.
If you have suggestions please open an issue, or join in on the discussion.
Quickstart
To get started use the SharedStateComponent
wrapper or StateView
component.
SharedStateComponent
Give your component any properties that implement SharedState
then wrap it with
SharedStateComponent
.
IMPORTANT: Changes must be handled in the component's change
method.
use yew::prelude::*; use yew_state::{SharedHandle, SharedStateComponent}; use yewtil::NeqAssign; #[derive(Clone, Default)] pub type AppState { pub count: usize, } pub struct Model { handle: SharedHandle<AppState>, } impl Component for Model { type Message = (); type Properties = SharedHandle<AppState>; fn create(handle: Self::Properties, _link: ComponentLink<Self>) -> Self { handle.reduce(|state| state.count = 1); // Magically set count to one for example Model { handle } } fn update(&mut self, msg: Self::Message) -> ShouldRender { true } fn change(&mut self, handle: Self::Properties) -> ShouldRender { self.handle.neq_assign(handle) } fn view(&self) -> Html { let onclick = self.handle.reduce_callback(|state| state.count += 1); let count = self.handle.state().count; html! { <p>{count}</p> <button onclick=onclick>{"+1"}</button> } } } pub type App = SharedStateComponent<Model>;
StateView
For something simpler, StateView
can handle shared state with less boilerplate.
Keep in mind you can't selectively re-render changes this way.
use yew::prelude::*; use yew_state::{ component::{view_state, StateView}, SharedHandle, }; type CountHandle = SharedHandle<usize>; fn view_counter() -> Html { html! { <> { view_display() } { view_input() } </> } } fn view_display() -> Html { let view = view_state(|handle: &CountHandle| { html! { <p>{handle.state()}</p> } }); html! { <StateView<CountHandle> view=view /> } } fn view_input() -> Html { let view = view_state(|handle: &CountHandle| { let onclick = handle.reduce_callback(|count| *count += 1); html! { <button onclick=onclick>{"+1"}</button> } }); html! { <StateView<CountHandle> view=view /> } }
Handling State
State handles provide an interface to shared state. SharedHandle
for basic access, while
StorageHandle
also does persistent local/session storage.
IMPORTANT: Changes to state do not take effect immediately! New state must be handled in the
component's change
method.
state
provides current state.
let state: &T = self.handle.state();
reduce
can be used from anywhere to modify shared state.
// SharedHandle<MyAppState> self.handle.reduce(move |state| state.user = new_user);
reduce_callback
allows modifying shared state from a callback.
// SharedHandle<usize> let onclick = self.handle.reduce_callback(|state| *state += 1); html! { <button onclick=onclick>{"+1"}</button> }
reduce_callback_with
provides the fired event as well.
let oninput = self .handle .reduce_callback_with(|state, i: InputData| state.user.name = i.value); html! { <input type="text" placeholder="Enter your name" oninput=oninput /> }
Custom Properties
SharedState
can be implemented for any properties.
TODO: This could be a macro.
#[derive(Clone, Properties)] pub struct Props { #[prop_or_default] handle: SharedHandle<AppState>, } impl SharedState for Props { type Handle = SharedHandle<AppState>; fn handle(&mut self) -> &mut Self::Handle { &mut self.handle } }
Persistence
To make state persistent use StorageHandle
instead of SharedHandle
. This requires state to also implement Serialize
,
Deserialize
, and Storable
.
TODO: This could be a macro.
use serde::{Serialize, Deserialize}; use yew_state::{Storable, Area}; #[derive(Clone, Default, Serialize, Deserialize)] struct T; impl Storable for T { fn area() -> Area { Area::Session // Defaults to Area::Local } }
Scoping
Sometimes it's useful to only share state within a specific scope. This may be done by providing a
custom scope to SharedStateComponent
or StateView
:
pub struct MyScope; pub struct MyComponent = SharedStateComponent<MyModel, MyScope>;
Example
This example demonstrates how two counters with different scopes can increment shared state independently.
use yew::prelude::*; use yew_state::{ component::{view_state, StateView}, SharedHandle, }; struct FooScope; struct BarScope; type CountHandle = SharedHandle<usize>; fn view_input<SCOPE: 'static>() -> Html { let view = view_state(|handle: &CountHandle| { let onclick = handle.reduce_callback(|count| *count += 1); html! { <button onclick=onclick>{"+1"}</button> } }); html! { <StateView<CountHandle, SCOPE> view=view /> } } fn view_display<SCOPE: 'static>() -> Html { let view = view_state(|handle: &CountHandle| { html! { <p>{handle.state()}</p> } }); html! { <StateView<CountHandle, SCOPE> view=view /> } } pub struct App; impl Component for App { type Message = (); type Properties = (); fn create(_props: Self::Properties, _link: ComponentLink<Self>) -> Self { Self } fn update(&mut self, msg: Self::Message) -> ShouldRender { true } fn change(&mut self, props: Self::Properties) -> ShouldRender { true } fn view(&self) -> Html { html! { <> <h1>{"FooScope"}</h1> { view_display::<FooScope>() } { view_input::<FooScope>() } <h1>{"BarScope"}</h1> { view_display::<BarScope>() } { view_input::<BarScope>() } </> } } }
Re-exports
pub use component::view_state; |
pub use component::SharedStateComponent; |
pub use component::StateView; |
pub use handle::SharedHandle; |
pub use handle::SharedState; |
pub use handle::StorageHandle; |
Modules
component | |
handle | Ergonomic interface with shared state. |
Enums
Area | An area to keep the data in. |
Traits
Storable | Allows state to be stored persistently in local or session storage. |