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}