createModule API
createModule: (opts: ModuleOpts) => SagaSlice
The secret sauce for saga-slice is the createModule
helper. It brings together types
, actions
, reducers
and sagas
into 1 file, dramatically reducing the amount of boilerplate needed to create an object store. At the simplest level, this can be used to manage your redux state; but if you wanted to add asynchronous functionality and deal with side-effects, you can also add sagas to the same file.
Sample Usage
How it works
When you create your saga slice, every reducer you create generates a subsequent
type
andaction
.Types are created in the format of
{name}/{key}
wherename
is derived fromopts.name
andtype
is the key name defined in reducer object.Reducers is a map of key/value pairs where key is used to generate types, and value is a reducer function.
Reducer functions have the signature
(state, payload) => {}
, wherestate
is clone of the current state, which can be directly manipulated, andpayload
is the data that was dispatched into the action.These functions are essentially immer producers.
The way that you manipulate the state is by hoisting to the object directly. There is no need to return anything. You can minimally pass an empty function to simply declare a type and action.
Take this example:
This would generate slice.actions.randomAction
and the todos/randomAction
type, which would be dispatched using the action function:
Because we did not tell the reducer to manipulate the state in any way, this would do nothing but serve as a way to create types and actions; however, if we wanted to do something with what we passed into the action, we could do the following:
Options
Option Name | Required | Data Type | Description |
---|---|---|---|
name | yes | string | Name used to identify reducer in the global state and format redux types. |
initialState | yes | object | The initial reducer state |
​reducers​ | yes | object | Map of reducers. Object values must be functions. |
​sagas​ | no | function | Sagas function (actions: any) => { [key: string]: SagaObject } |
​takers​ | no | object|string|generator | Takers to be used. Can be a string that names a redux saga taker such as takeLatest . Can also be a generator function or map of { reducerName: takeLatest } or { takeLatest: ['reducerName'] } . |
Actions Map
The resulting object from a createModule
has an actions
property which is a map of functions to dispatch redux actions. Action functions have a type
property which returns the generated type. Take the following example:
slice.actions
would have the following:
Sagas
Finally, we can define sagas. This entire section assumes that you have a basic understand of redux sagas. The sagas option is a function with the signature (actions) => ({})
. The returned value should be an object of key value pairs where keys are action types to be passed into redux saga effects, and value is either a generator function or configuration object. This option is the only option not required for creating a saga-slice module.
info
Meta programming ahead:
In the following example, you will notice a strange implementation that will not immediately make sense unless you understand the magic behind it.
Javascript is quirky and allows us to do weird things. Sometimes that's a good thing and we can do things like:
This happens because xyz.toString()
generates the string 'function xyz () {}'
, and javascript coerces types into string in order to successfully create an object. We leverage this behavior for the creation of our saga configuration object.
.... wait... what?
Let's take it step by step by addressing the meta programming:
Under the hood, when actions are created, certain builtins are overwritten. In particular, the default Function.prototype.toString
is overwritten for actions to always return its type. Essentially, actions.fetch.type
is the same as actions.fetch.toString()
. The latter is added for the convenience of using our actions as both keys and functions.
Theoretical example:
You could technically also do:
Really, whatever you feel comfortable with. The convenience of passing the action function is there so that you don't have to guess or potentially misspell the action types while you're writing sagas.
Next, let's address the saga configuration object. A saga config can either be a generator function, or a config with a generator function and/or a taker. If the value is a generator function, the saga will be ran using the takeEvery
effect.
Option A: Generator function
Option B: Config object
For option B, essentially, any taker that can follow the signature function (type, saga)
can be used to run the saga. In the cases where a taker doesn't exactly match that signature, such as debounce
, you can always bind it to fulfill that effect's required arity.
See redux sagas API reference for more details on what you can use.
Takers
Takers can be declared as part of configuration options in a multitude of ways. This is good for setting the default taker for all you sagas, or setting a taker for one or many of your sagas. You can use it as follows:
Allowed non-custom takers
When takers
option is a string, or when a value in takers
is an array, you can only apply the following effects:
takeEvery
takeLatest
takeMaybe
takeLeading
debounce
throttle
The following are acceptable configuration options