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. ```rust 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, }

impl Component for Model { type Message = (); type Properties = SharedHandle;

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; ```

StateView

For something simpler, StateView can handle shared state with less boilerplate.

Keep in mind you can't selectively re-render changes this way.

```rust use yew::prelude::*; use yewstate::{viewstate, StateView, SharedHandle};

type CountHandle = SharedHandle;

fn viewcounter() -> Html { html! { <> { viewdisplay() } { view_input() } } }

fn viewdisplay() -> Html { let view = viewstate(|handle: &CountHandle| { html! {

{handle.state()}

} }); html! { view=view /> } }

fn viewinput() -> Html { let view = viewstate(|handle: &CountHandle| { let onclick = handle.reduce_callback(|count| *count += 1); html! { } }); html! { 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. rust let state: &T = self.handle.state();

reduce can be used from anywhere to modify shared state. rust // SharedHandle<MyAppState> self.handle.reduce(move |state| state.user = new_user);

reduce_callback allows modifying shared state from a callback. rust // 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. ```rust let oninput = self .handle .reducecallbackwith(|state, i: InputData| state.user.name = i.value);

html! { } ```

Custom Properties

SharedState can be implemented for any properties.

TODO: This could be a macro. ```rust

[derive(Clone, Properties)]

pub struct Props { #[propordefault] handle: SharedHandle, }

impl SharedState for Props { type Handle = SharedHandle;

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. ```rust 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:

rust pub struct MyScope; pub struct MyComponent = SharedStateComponent<MyModel, MyScope>;

Example

This example demonstrates how two counters with different scopes can increment shared state independently. ```rust use yew::prelude::*; use yewstate::{viewstate, StateView, SharedHandle};

struct FooScope; struct BarScope;

type CountHandle = SharedHandle;

fn viewinput() -> Html { let view = viewstate(|handle: &CountHandle| { let onclick = handle.reducecallback(|count| *count += 1); html! { } }); html! { view=view /> } } fn viewdisplay() -> Html { let view = view_state(|handle: &CountHandle| { html! {

{handle.state()}

} }); html! { 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>() }
        </>
    }
}

} ```