yew_state/component/
wrapper.rs

1//! Wrapper for components with shared state.
2use std::rc::Rc;
3
4use yew::{
5    agent::{Bridge, Bridged},
6    prelude::*,
7};
8
9use crate::handle::{Handle, SharedState, WrapperHandle};
10use crate::handler::{HandlerLink, Reduction, ReductionOnce, StateHandler};
11use crate::service::*;
12
13type PropHandle<SHARED> = <SHARED as SharedState>::Handle;
14type PropHandler<SHARED> = <PropHandle<SHARED> as Handle>::Handler;
15type Model<T> = <PropHandler<T> as StateHandler>::Model;
16
17#[doc(hidden)]
18pub enum SharedStateComponentMsg<SHARED>
19where
20    SHARED: SharedState,
21    <SHARED as SharedState>::Handle: WrapperHandle,
22    PropHandler<SHARED>: 'static,
23{
24    /// Recieve new local state.
25    /// IMPORTANT: Changes will **not** be reflected in shared state.
26    SetLocal(Rc<Model<SHARED>>),
27    SetLink(HandlerLink<PropHandler<SHARED>>),
28    /// Update shared state.
29    Apply(Reduction<Model<SHARED>>),
30    ApplyOnce(ReductionOnce<Model<SHARED>>),
31    /// Do nothing.
32    Ignore,
33}
34
35/// Component wrapper for managing messages and state handles.
36///
37/// Wraps any component with properties that implement `SharedState`:
38/// ```
39/// pub type MyComponent = SharedStateComponent<MyComponentModel>;
40/// ```
41///
42/// A scope may be provided to specify where the state is shared:
43/// ```
44/// // This will only share state with other components using `FooScope`.
45/// pub struct FooScope;
46/// pub type MyComponent = SharedStateComponent<MyComponentModel, FooScope>;
47/// ```
48///
49/// # Important
50/// By default `StorageHandle` and `GlobalHandle` have different scopes. Though not enforced,
51/// components with different handles should not use the same scope.
52pub struct SharedStateComponent<C, SCOPE = PropHandler<<C as Component>::Properties>>
53where
54    C: Component,
55    C::Properties: SharedState + Clone,
56    PropHandle<C::Properties>: WrapperHandle,
57    SCOPE: 'static,
58{
59    props: C::Properties,
60    bridge: Box<dyn Bridge<StateService<PropHandler<C::Properties>, SCOPE>>>,
61    link_set: bool,
62    state_set: bool,
63}
64
65impl<C, SCOPE> Component for SharedStateComponent<C, SCOPE>
66where
67    C: Component,
68    C::Properties: SharedState + Clone,
69    <C::Properties as SharedState>::Handle: Clone + WrapperHandle,
70{
71    type Message = SharedStateComponentMsg<C::Properties>;
72    type Properties = C::Properties;
73
74    fn create(mut props: Self::Properties, link: ComponentLink<Self>) -> Self {
75        use SharedStateComponentMsg::*;
76        // Bridge to receive new state.
77        let callback = link.callback(|msg| match msg {
78            ServiceOutput::Service(ServiceResponse::State(state)) => SetLocal(state),
79            ServiceOutput::Service(ServiceResponse::Link(link)) => SetLink(link),
80            ServiceOutput::Handler(_) => Ignore,
81        });
82        let mut bridge = StateService::bridge(callback);
83        // Subscribe to state changes.
84        bridge.send(ServiceInput::Service(ServiceRequest::Subscribe));
85        // Connect our component callbacks.
86        props
87            .handle()
88            .set_callbacks(link.callback(Apply), link.callback(ApplyOnce));
89
90        Self {
91            props,
92            bridge,
93            state_set: Default::default(),
94            link_set: Default::default(),
95        }
96    }
97
98    fn update(&mut self, msg: Self::Message) -> ShouldRender {
99        use SharedStateComponentMsg::*;
100        match msg {
101            Apply(reduce) => {
102                self.bridge
103                    .send(ServiceInput::Service(ServiceRequest::Apply(reduce)));
104                false
105            }
106            ApplyOnce(reduce) => {
107                self.bridge
108                    .send(ServiceInput::Service(ServiceRequest::ApplyOnce(reduce)));
109                false
110            }
111            SetLocal(state) => {
112                self.props.handle().set_state(state);
113                self.state_set = true;
114                true
115            }
116            SetLink(link) => {
117                self.props.handle().set_link(link);
118                self.link_set = true;
119                true
120            }
121            Ignore => false,
122        }
123    }
124
125    fn change(&mut self, mut props: Self::Properties) -> ShouldRender {
126        *props.handle() = self.props.handle().clone();
127        self.props = props;
128        true
129    }
130
131    fn view(&self) -> Html {
132        if self.link_set && self.state_set {
133            let props = self.props.clone();
134            html! {
135                <C with props />
136            }
137        } else {
138            html! {}
139        }
140    }
141}