reactive_state/
provider.rs

1use crate::Store;
2use std::{cell::RefCell, fmt::Debug, rc::Rc};
3use yew::{
4    html, html::ChildrenRenderer, virtual_dom::VChild, ChildrenWithProps, Component, ComponentLink,
5    Properties,
6};
7
8#[derive(Clone)]
9pub struct MapStateToProps<C: Component, State>(
10    fn(&Rc<State>, &C::Properties) -> Option<C::Properties>,
11);
12
13impl<C, State> PartialEq for MapStateToProps<C, State>
14where
15    C: Component,
16{
17    fn eq(&self, other: &MapStateToProps<C, State>) -> bool {
18        (self.0 as *const ()) == (other.0 as *const ())
19    }
20}
21
22impl<C, State> MapStateToProps<C, State>
23where
24    C: Component,
25{
26    pub fn new(function: fn(&Rc<State>, &C::Properties) -> Option<C::Properties>) -> Self {
27        Self(function)
28    }
29
30    pub fn perform(&self, state: &Rc<State>, props: &C::Properties) -> Option<C::Properties> {
31        (self.0)(state, props)
32    }
33}
34
35impl<C: Component, State> Debug for MapStateToProps<C, State> {
36    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
37        write!(f, "MapStateToProps(function @ {:p})", &self.0)
38    }
39}
40
41#[derive(Clone, Properties)]
42struct Props<C, State, Action>
43where
44    C: Component + Clone,
45    C::Properties: PartialEq,
46    State: Clone,
47    Action: Clone,
48{
49    pub map_state_to_props: MapStateToProps<C, State>,
50    pub store: Rc<RefCell<Store<State, Action, (), ()>>>,
51    pub children: ChildrenWithProps<C>,
52}
53
54impl<C, State, Action> Debug for Props<C, State, Action>
55where
56    C: Component + Clone,
57    C::Properties: PartialEq,
58    State: Clone,
59    Action: Clone,
60{
61    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
62        write!(
63            f,
64            "Provider::Props{{map_state_to_props: {0:?}, store @ {1:p}, children: {2:?}}}",
65            self.map_state_to_props, &*self.store, self.children
66        )
67    }
68}
69
70impl<C, State, Action> PartialEq for Props<C, State, Action>
71where
72    C: Component + Clone,
73    C::Properties: PartialEq,
74    State: Clone,
75    Action: Clone,
76{
77    fn eq(&self, other: &Props<C, State, Action>) -> bool {
78        // TODO: this should also include the children, but it's not currently possible due to https://github.com/yewstack/yew/issues/1216
79        Rc::ptr_eq(&self.store, &other.store)
80            && self.map_state_to_props == other.map_state_to_props
81            && self.children == other.children
82    }
83}
84
85enum Msg<State> {
86    StateUpdate(Rc<State>),
87}
88
89struct Provider<C, State, Action, Event>
90where
91    C: Component + Clone,
92    C::Properties: PartialEq,
93    State: Clone + 'static,
94    Action: Clone + 'static,
95    Event: Clone + 'static,
96{
97    props: Props<C, State, Action>,
98    children: ChildrenWithProps<C>,
99    _link: ComponentLink<Provider<C, State, Action, Event>>,
100    _callback: crate::Callback<State, Event>,
101}
102
103impl<C, State, Action, Event> Provider<C, State, Action, Event>
104where
105    C: Component + Clone,
106    C::Properties: PartialEq,
107    State: Clone + 'static,
108    Action: Clone + 'static,
109    Event: Clone + 'static,
110{
111    fn update_children_props(
112        children: &ChildrenWithProps<C>,
113        state: &Rc<State>,
114        map_state_to_props: &MapStateToProps<C, State>,
115    ) -> Option<ChildrenWithProps<C>> {
116        // TODO: only make the children vec if props changed
117        // alternatively request an iter_mut implementation for ChildrenWithProps...
118        let mut children_vec: Vec<VChild<C>> = children.iter().collect();
119        let mut child_props_changed = false;
120
121        for child in &mut children_vec {
122            if let Some(properties) = map_state_to_props.perform(state, &child.props) {
123                child.props = properties;
124                child_props_changed = true;
125            }
126        }
127
128        if child_props_changed {
129            Some(ChildrenRenderer::new(children_vec))
130        } else {
131            None
132        }
133    }
134}
135
136impl<C, State, Action, Event> Component for Provider<C, State, Action, Event>
137where
138    C: Component + Clone,
139    C::Properties: PartialEq,
140    State: Clone + 'static,
141    Action: Clone + 'static,
142    Event: Clone + 'static,
143{
144    type Message = Msg<State>;
145    type Properties = Props<C, State, Action>;
146
147    fn create(props: Props<C, State, Action>, link: yew::ComponentLink<Self>) -> Self {
148        let callback = link.callback(|(state, _)| Msg::StateUpdate(state)).into();
149
150        let children = match Self::update_children_props(
151            &props.children,
152            &props.store.borrow().state(),
153            &props.map_state_to_props,
154        ) {
155            None => props.children.clone(),
156            Some(children) => children,
157        };
158
159        Self {
160            props,
161            children,
162            _link: link,
163            _callback: callback,
164        }
165    }
166
167    fn update(&mut self, msg: Msg<State>) -> yew::ShouldRender {
168        match msg {
169            Msg::StateUpdate(state) => {
170                let result: Option<ChildrenWithProps<C>> = Self::update_children_props(
171                    &self.props.children,
172                    &state,
173                    &self.props.map_state_to_props,
174                );
175                match result {
176                    Some(new_children) => {
177                        self.children = new_children;
178                        true
179                    }
180                    None => false,
181                }
182            }
183        }
184    }
185
186    fn change(&mut self, props: Props<C, State, Action>) -> yew::ShouldRender {
187        if self.props != props {
188            if self.props.children != props.children {
189                match Self::update_children_props(
190                    &props.children,
191                    &props.store.borrow().state(),
192                    &props.map_state_to_props,
193                ) {
194                    None => self.children = props.children.clone(),
195                    Some(children) => self.children = children,
196                };
197            }
198
199            self.props = props;
200            true
201        } else {
202            false
203        }
204    }
205
206    fn view(&self) -> yew::Html {
207        html! { <>{ self.children.clone() }</> }
208    }
209}