Thunks should support sync or async workflow
See original GitHub issueThere are scenarios where it would be convenient to call an action from another action. This helps keep code DRY and avoids a complicated maze of listeners to accomplish the same thing.
For example, an action could apply branching logic to decide which of 3 different updates to do. There may be individual actions for each of these updates, with their own data-transformation logic. Therefore it’s cleaner and DRYer to call these actions rather than duplicate their code.
I propose passing appending ‘actions’ as a 3rd argument to action() & actionOn(). This usually won’t be needed, just as storeState isn’t often needed, but would be available when it is…
action: (state, payload, actions) => {...}
actionOn: (state, target, actions) => {...}
Thunks, (a type of action) receive an actions arg, so this is not-inconsistent.
Here’s a contrived example, using the proposed 3rd argument to the handler:
updateValue: action((state, payload, actions) => {
const { data, category } = payload;
const { rights } = state.user;
if (category === 'top') {
if (rights === 'admin') {
actions.setTopValue(data)
} else {
actions.setCategoryIfAllowed(payload)
}
} else {
actions[`set${category}Value`](data)
}
})
It’s not a great example, but hopefully it illustrates how branching to other actions can help keep things DRY and simple.
Why not put this logic in the calling code? If this action is called from multiple places, I don’t want to repeat this logic.
Why not create a helper method? This simple branching logic is based on data already in state, so an action seems the logic place for it. An action should be able to handle such logic, without replicating code from other actions that can also be called directly in other scenarios.
Issue Analytics
- State:
- Created 4 years ago
- Comments:13 (13 by maintainers)

Top Related StackOverflow Question
I appreciate your desire to keep things DRY, however, I think we should consider dispatching actions within an action an anti-pattern. It it essentially the equivalent of dispatching an action from within a Redux reducer, and would suffer the same pitfalls.
The primary motivation you seem to have is avoiding duplication of code. Perhaps this could solved in a similar manner to a “pure” redux reducer strategy. Instead of delegating to reducers however, we could delegate to functions.
If it gets tedious, or you discover patterns in your code, then perhaps consider to write helpers. For example;
Which could be used like so:
In this example, the
removeInProgressItem‘function’ is really an ‘action’ that lives outside the model. The anti-pattern/limitation has not been avoided - just worked around.You noted this is “the equivalent of dispatching an action from within a Redux reducer”. Yes and no. Normal reducers are ‘listeners’. Every reducer is called for every action, so multiple reducers can act on the same action-name. EP maps each action to a specific reducer, which is what creates the limitation of one reducer per-action
My own Redux wrappers also mapped one-action to one-reducer. When I needed a second reducer to fire, I triggered it. In simplified form:
reducers.doSomething(action). Remembering this made me realize that I can do the same thing with EP model methods!Instead of using an external function, this example calls another model action directly. Since I only want to trigger other actions within the same model/slice, this should work for me.