Ducks
Ducks are objects that describe redux state transitions.
The goal is to isolate all code for every action in a single place. Making transitions simpler to maintain, and quicker to analyze.
const increment = {
// This is typically what you would use for the name of your action
// creator.
name: 'increment',
// Your action creator
action: payload => ({ payload }),
// Your transducer,
// You've seen them as the case blocks within switch statements.
reducer: (s, { payload }) =>
payload
? s + payload
: s + 1
}
Modules
Ducks get organized into modules. Each module needs an initial state, a few selectors for selecting information (functions that recieve the modules state as the argument), and a list of ducks for modifying pieces of that state.
const increment = { ... };
const decrement = { ... };
const module = {
// The initial state should reflect the state that your ducks will be
// manipulating.
init: 0,
// A map of selectors, for extracting state from the above contract.
select: {
val : s => s,
succ : s => s + 1,
pred : s => s - 1,
},
// The list of all of the ducks that interact at this level with the
// state.
ducks: [
increment,
decrement,
]
};
Deux
The deux
function is very powerful. It has the ability to merge
modules and organize our branches of state. This is done by recursively
descending the spec we provide. The return value, uses the shape of the tree
to annotate and patch the action and reducer functions to account for all of
the nesting.
This enables us to reuse state, in multiple places, such as the
counter
in this example:
const app = deux({
example: {
phonebook,
counter,
},
otherState: {
todo,
counter,
}
});
Rere
Once we have a state declared the way we want, we'll call rere
on it to extract everything we'll need to plug in to our redux store.
For convinience sake, there is a function called reredeux
which
composes both rere
and deux
. Often times you'll
only need this function, since you can technically use it anywhere
deux
is used.
const counter = {
init: 0,
select: { val: s => s },
ducks: [ increment, decrement ]
};
const { reducer, init, actions, select } =
reredeux({
data: {
counter,
},
otherData: {
counter,
}
});
const store = createStore(reducer, init);
store.getState();
// { data: { counter: 0 }, otherData: { counter: 0 } }
Actions and Selectors
Once you've created your store, you'll have access to actions
and select
. Both objects mimic the shape of your state tree.
store.dispatch(actions.data.counter.increment());
store.dispatch(actions.data.counter.increment());
select.data.counter.val(store.getState()) // 2
select.otherData.counter.val(store.getState()) // 0
store.dispatch(actions.data.counter.decrement());
select.data.counter._(store.getState()) // 1
select.otherData.counter.val(store.getState()) // 0
NOTE: there is a convinience selector added to every level of the
state tree. This selector is denoted with the _
character, and
will return all of the state at that level on down.