1. You can use strings for action types, no more need to import constants.
2. No need for action creators for simple actions, you can just create the action object inline and Flow will validate its shape.
3. You can be sure that reducers are unpacking properties that actually exist from the actions.
In other words, your example could simply be:
// types.js
type Action =
| { type: 'ADD_TODO', text: string }
// TodoList.js
dispatch({ type: 'ADD_TODO', text })
...and Flow would confirm at 'compile time' that it matches the contract dispatch expects.This approach has scaled up extremely well for me and no longer feels verbose.
And then I found myself writing action creators on purpose, not because I saw it in some tutorial, but because it actually does remove some duplication.
This was also missing from my education for a long time: https://github.com/acdlite/flux-standard-action#actions
I had no idea why I was using a "payload" key, instead of just putting all the values in the top-level object. But this convention makes a lot of sense now.
That said, you are absolutely encouraged to abstract over that process as much as you want! For example, the https://github.com/acdlite/redux-actions library will generate both the action constants and the action creators for you, and there's many more similar utilities.
1. Write helpers. 2. Write middleware.
The first is zero magic, and allows you to opt-in to abstractions as needed. The latter is more powerful and can remove pretty much any boilerplate, but you have to be careful about introducing too much magic.
Redux isn't much different than any other code you might write. If you prematurely abstract, you could create a leaky abstraction, and you'll have to unroll the whole thing. If you don't abstract at all, and copy-pasta everything, you're also going to have a bad time.
- Put all the action types, action types, and reducers for a given portion of the application into one file. This is known as the "ducks" structure, per https://github.com/erikras/ducks-modular-redux .
- Use a utility library like https://github.com/acdlite/redux-actions to generate action types and action creators for you
- Write middleware to manage common logic for your application such as API calls
- Skip writing action creators and declaring action constants, and just have components do `this.props.dispatch({type : "SOME_ACTION"})`.
I don't recommend the last approach, but it's _totally_ up to you, especially because what you regard as "too much boilerplate" is going to be a personal opinion. One person's "too much boilerplate" is another person's "explicit and easy to trace through the system".
Start with
actionNames.txt
ADD_TODO
REMOVE_TODO
Cat that file to actionTypes.js and actions.js. In actionTypes.js, expand:<line> to:
export const <line> = "<line>"
And in actions.js, expand <line> to something like: export const (camelcase-string <line>) = () => {
return {
type: types.<line>,
};
};
Also instead of importing actions and actionTypes individually, I'll generally do: import * as actions from './actions';
import * as types from './actionTypes';
dispatch(actions.addTodo(...));Also I use my json-mobx library to get serialisation and undo/redo. Example: https://github.com/danielearwicker/baltar
First we enumerate our actions as constants...
Then we need to create an "action creator" somewhere: Then we need to import that action and dispatch it when we need it... Now even the same nonsense for reducers...