Redux provides a convenient helper for combining many reducers called combineReducer
, but it focuses in on specific attributes on our state, making it incompatible with using the State ADT. We would like a way to avoid keeping all of our reducers in a single file and want the ability to still combine them in a manner that works with the State
ADT.
So we will put together our own helper that we also call combineReducers
, but explore how we can use the First
Monoid and mreduceMap
to get us all the power that the Redux helper provides, but setup for our unique needs. As a bonus we will get a sneak peak of the power of using the flip
combinator to create easy to read compositions without pesky argument juggling
Instructor: [00:00] We import in this new start transition off the default of our game model which uses these arrays to populate this cards array, by using this seed to randomly select nine of the generated cards, marking them all as selected.
[00:13] Back in our game model, we see that start takes a unit to a state app seeded unit. It's the Kleisli that first runs this pick cards transition to generate our cards, followed by this MarkedCardsSelected transition that adds selected true to each of the picked cards.
[00:28] We also export this new MarkedCardsUnselected function that removes the selected attribute for each of the picked cards, undoing what MarkedCardsSelected did.
[00:38] Our reducer files are where all things come together. We not only bring in our state transitions, but we also define our actions, export our action creators, and define and export our reducer functions like in this turn reducer file.
[00:51] Over in our game reducer, we see the same pattern. With our state transitions, action names, action creators, and of course the actual reducer. While there's still some boilerplate, it is considerably less than what we're used to. Over in the main reducer that integrates with Redux, we see that we currently only have it using this turn reducer as we see down below. We would like to also use this game reducer or any other reducer that we opt to include.
[01:20] The first thing we'll do is group these together inside of an array, which we'll simply call Reducers, defining reducers as an array of reducer. Then we just package up game and turn inside of an array. Now we need to combine these reducers in a way that's useful for our flow.
[01:38] Over in helpers we'll export a brand-new helper function which we'll call CombineReducers. We define CombineReducers as a function that takes an array of reducer as we have it defined here and then takes them into a single reducer to work with the implementation we already have in our main reducer file.
[01:58] We implement by bringing in our reducers, followed by a given action to match the siggy for reducer. Then we reach for the crocks and reduceMap that allows us to fold using a monoid by first running our values through a function before concatenating the monoid. We'll fold using the first monoid that retains the first valid value no matter how many times it's concatenated using the maybe data type to determine validity.
[02:22] To get the maybe we need we call the reducer with our action using apply to to apply the action to each reducer inside of our reducers array, which we provide as the third argument to mreduceMap.
[02:34] That's it. We can now create a single reducer from many reducers that will work the same over in our main reducer. We just pull CombineReducers off of our helpers module and then wrap our reducers with a call to CombineReducers. Update our signature, as it is now a single reducer, and then replace turn with our new reducers function, leaving us with the same general flow.
[02:58] Now we can better organize our state transitions into as many reducer files as we see fit, which we can demonstrate by calling reducer with our mock state and then the start game action creator, getting back our expected transition state. When we focus in on our cards array, we find them all selected with our cards array having a length of nine.
[03:19] Let's simulate what using Redux as a dispatching system would look like. We first use this game state variable to hold our transition state by executing our start transaction with our mock state. Then we just use it down below as the starting state for our reducer. Then dispatch the next action in our chain to unselect all of the cards, finding all of our cards now unselected.
[03:42] In our game state we captured this action by chaining in the state transition associated to it. Then dispatch the next action to select a specific card, like this orange circle. As we see, orange circle has been marked as selected and our moves and left attributes have been transitioned as well.
[04:01] With a working implementation, let's see about some refactors we can do to avoid some of the argument gymnastics we've got going on. Our reducers are applied last, but we take them in first. We take in action last, but it's actually the first argument to be applied. It seems like we need to flip how these arguments are applied, which we can do with the crocks flip combiner.
[04:23] Using flip, we can remove reducers from our arguments and also remove it from mreduceMap. It becomes a function ready to accept our reducers. Giving it a save we see it all still works.
[04:35] But why stop here? By using compose we can remove the need to define actions as well by first calling apply to with no arguments ready to accept the action. Then the resulting function will become the function needed for the second argument of mreduceMap, returning a function ready to accept our array of reducers.
[04:55] Seeing that this all still works with select card, we pop it off the stack and verify with the hide all cards action, removing its associated state transaction. For completeness, we see that our silly verb still works as expected.