reactive_state/
reducer.rs

1use std::rc::Rc;
2
3/// A wrapper for a function that implements the [Reducer](Reducer)
4/// trait.
5///
6/// ## Example
7///
8/// ```
9/// # #[derive(Clone)]
10/// # struct MyState {
11/// #     pub variable: bool
12/// # }
13/// #
14/// # enum MyAction {
15/// #     SomeAction
16/// # }
17/// #
18/// # enum MyEvent {
19/// #     SomeEvent
20/// # }
21/// #
22/// # enum MyEffect {
23/// #     SomeEffect
24/// # }
25/// use reactive_state::{ReducerFn, ReducerResult, Reducer};
26/// use std::rc::Rc;
27///
28/// let reducer: ReducerFn<MyState, MyAction, MyEvent, MyEffect> = |state, action| {
29///     let mut events = Vec::new();
30///
31///     let new_state = match action {
32///         MyAction::SomeAction => {
33///             // create a new state to mutate and return
34///             let mut new_state = MyState::clone(state);
35///             new_state.variable = true;
36///
37///             // An event needs to be produced to notify
38///             // subscribers that the state has changed.
39///             events.push(MyEvent::SomeEvent);
40///
41///             Rc::new(new_state)
42///         }
43///     };
44///
45///     ReducerResult {
46///         state: new_state,
47///         events: events,
48///         effects: vec![],
49///     }
50/// };
51///
52/// let state1 = Rc::new(MyState {
53///     variable: false
54/// });
55///
56/// let result = reducer.reduce(&state1, &MyAction::SomeAction);
57/// let state2 = &result.state;
58///
59/// assert_eq!(false, state1.variable);
60/// assert_eq!(true, state2.variable);
61/// ```
62///
63/// For a more comprehensive example of how reducers are used in the
64/// context of the entire system, see [reactive_state](crate).
65pub type ReducerFn<State, Action, Event, Effect> =
66    fn(&Rc<State>, &Action) -> ReducerResult<State, Event, Effect>;
67
68impl<T, State, Action, Event, Effect> Reducer<State, Action, Event, Effect> for T
69where
70    T: Fn(&Rc<State>, &Action) -> ReducerResult<State, Event, Effect>,
71{
72    fn reduce(
73        &self,
74        prev_state: &Rc<State>,
75        action: &Action,
76    ) -> ReducerResult<State, Event, Effect> {
77        (self)(prev_state, action)
78    }
79}
80
81/// Using the [reduce()](Reducer::reduce()) method, implementors of
82/// this trait take an `Action` submitted to a store via
83/// [Store::dispatch()](crate::Store::dispatch()) and modifies the
84/// `State` in the store, producing a new `State`, and also producing
85/// events and effects associated with the `Action` and state
86/// modifications that occurred.
87///
88/// For an example of how a reducer function should work, see
89/// [ReducerFn](ReducerFn). For an example of how to use one in
90/// conjunction with a [Store](crate::Store), see
91/// [reactive_state](crate).
92pub trait Reducer<State, Action, Event, Effect> {
93    /// Take an `Action` submitted to a store via
94    /// [Store::dispatch()](crate::Store::dispatch()) and modifies the
95    /// `prev_state`, producing a new `State`, and also producing
96    /// events associated with the `Action` and state modifications
97    /// that occurred.
98    ///
99    /// **Note:** If no `Event`s are returned then it is assumed that
100    /// the state has not changed, and store listeners do not need to
101    /// be notified.
102    ///
103    /// **Note:** If all `Events`s are `Event::none()`, then it is
104    /// also assumed that the state has not changed, and store
105    /// listeners do not need to be notified.
106    ///
107    /// This method should be a pure function, with any required side
108    /// effects being emmitted via the returned
109    /// [ReducerResult](ReducerResult).
110    ///
111    /// `Events`s should generally be treated purely as a notification
112    /// that some subset of the state has been modified, such that
113    /// playing the events and state transitions in reverse will
114    /// result in the same application behaviour.
115    ///
116    /// `Effect`s are side effects invoked as a result of the action,
117    /// these may involve dispatching further actions, or modifying
118    /// some other part of the system that the store is involved with.
119    /// `Effect`s are processed using
120    /// [Middleware](crate::middleware::Middleware) which has been
121    /// added to the [Store](crate::Store).
122    fn reduce(
123        &self,
124        prev_state: &Rc<State>,
125        action: &Action,
126    ) -> ReducerResult<State, Event, Effect>;
127}
128
129/// The result of a [Reducer::reduce()] function.
130///
131/// `Events`s should generally be treated purely as a notification
132/// that some subset of the state has been modified, such that
133/// playing the events and state transitions in reverse will
134/// result in the same application behaviour.
135///
136/// `Effect`s are side effects invoked as a result of the action,
137/// these may involve dispatching further actions, or modifying
138/// some other part of the system that the store is involved with.
139/// `Effect`s are processed using [Middleware](crate::middleware::Middleware)
140/// which has been added to the [Store](crate::Store).
141pub struct ReducerResult<State, Event, Effect> {
142    pub state: Rc<State>,
143    pub events: Vec<Event>,
144    pub effects: Vec<Effect>,
145}
146
147impl<State, Event, Effect> Default for ReducerResult<State, Event, Effect>
148where
149    State: Default,
150{
151    fn default() -> Self {
152        Self {
153            state: Rc::new(State::default()),
154            events: vec![],
155            effects: vec![],
156        }
157    }
158}
159
160// TODO: create a zero cost macro version of this #17
161/// A [Reducer] composed of multiple reducers.
162pub struct CompositeReducer<State, Action, Event, Effect> {
163    reducers: Vec<Box<dyn Reducer<State, Action, Event, Effect>>>,
164}
165
166impl<State, Action, Event, Effect> CompositeReducer<State, Action, Event, Effect> {
167    /// Create a new [CompositeReducer].
168    pub fn new(reducers: Vec<Box<dyn Reducer<State, Action, Event, Effect>>>) -> Self {
169        CompositeReducer { reducers }
170    }
171}
172
173impl<State, Action, Event, Effect> Reducer<State, Action, Event, Effect>
174    for CompositeReducer<State, Action, Event, Effect>
175{
176    fn reduce(
177        &self,
178        prev_state: &Rc<State>,
179        action: &Action,
180    ) -> ReducerResult<State, Event, Effect> {
181        let mut sum_result: ReducerResult<State, Event, Effect> = ReducerResult {
182            state: prev_state.clone(),
183            events: Vec::new(),
184            effects: Vec::new(),
185        };
186
187        for reducer in &self.reducers {
188            let result = reducer.reduce(&sum_result.state, action);
189            sum_result.state = result.state;
190            sum_result.events.extend(result.events);
191            sum_result.effects.extend(result.effects);
192        }
193
194        sum_result
195    }
196}
197
198#[cfg(test)]
199mod tests {
200    use crate::{CompositeReducer, Reducer, ReducerResult};
201    use std::rc::Rc;
202
203    struct TestState {
204        emitted_events: Vec<TestEvent>,
205    }
206
207    impl Default for TestState {
208        fn default() -> Self {
209            TestState {
210                emitted_events: Vec::new(),
211            }
212        }
213    }
214
215    struct TestAction;
216
217    #[derive(Debug, Clone, PartialEq)]
218    enum TestEvent {
219        Event1,
220        Event2,
221    }
222
223    #[derive(Debug, PartialEq)]
224    enum TestEffect {
225        Effect1,
226        Effect2,
227    }
228
229    struct Reducer1;
230
231    impl Reducer<TestState, TestAction, TestEvent, TestEffect> for Reducer1 {
232        fn reduce(
233            &self,
234            prev_state: &Rc<TestState>,
235            _action: &TestAction,
236        ) -> crate::ReducerResult<TestState, TestEvent, TestEffect> {
237            let mut emitted_events = prev_state.emitted_events.clone();
238            emitted_events.push(TestEvent::Event1);
239            let state = Rc::new(TestState { emitted_events });
240
241            ReducerResult {
242                state,
243                events: vec![TestEvent::Event1],
244                effects: vec![TestEffect::Effect1],
245            }
246        }
247    }
248
249    struct Reducer2;
250
251    impl Reducer<TestState, TestAction, TestEvent, TestEffect> for Reducer2 {
252        fn reduce(
253            &self,
254            prev_state: &Rc<TestState>,
255            _action: &TestAction,
256        ) -> crate::ReducerResult<TestState, TestEvent, TestEffect> {
257            let mut emitted_events = prev_state.emitted_events.clone();
258            emitted_events.push(TestEvent::Event2);
259            let state = Rc::new(TestState { emitted_events });
260
261            ReducerResult {
262                state,
263                events: vec![TestEvent::Event2],
264                effects: vec![TestEffect::Effect2],
265            }
266        }
267    }
268
269    #[test]
270    fn composite_reducer() {
271        let reducer = CompositeReducer::new(vec![Box::new(Reducer1), Box::new(Reducer2)]);
272
273        let result = reducer.reduce(&Rc::new(TestState::default()), &TestAction);
274        assert_eq!(
275            result.state.emitted_events,
276            vec![TestEvent::Event1, TestEvent::Event2]
277        );
278        assert_eq!(result.events, vec![TestEvent::Event1, TestEvent::Event2]);
279        assert_eq!(
280            result.effects,
281            vec![TestEffect::Effect1, TestEffect::Effect2]
282        );
283    }
284}