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
typeandaction.Types are created in the format of
{name}/{key}wherenameis derived fromopts.nameandtypeis 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) => {}, wherestateis clone of the current state, which can be directly manipulated, andpayloadis 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:
takeEverytakeLatesttakeMaybetakeLeadingdebouncethrottle
The following are acceptable configuration options