To reduce boilerplate in generating and checking Redux action objects.
To reduce errors when creating Redux action types and reducers.
To allow action’s shape to be specified.
-
The Flux/Redux approach is to define action types as string constants.
Each action type name is spelled out twice.
There is a possibility that the constant or the value is misspelled.
export const ADD_TODO = 'ADD_TODO'
export const DELETE_TODO = 'DELETE_TODO'
export const EDIT_TODO = 'EDIT_TODO'
export const COMPLETE_TODO = 'COMPLETE_TODO'
export const COMPLETE_ALL = 'COMPLETE_ALL'
export const CLEAR_COMPLETED = 'CLEAR_COMPLETED'
-
In Elm, you declare your action as an algebraic data type.
This also lets you specify the shape of your action,
as well as providing type safety.
type Action
= AddTodo String
| DeleteTodo Int
| EditTodo Int String
| CompleteTodo Int
| CompleteAll
| ClearCompleted
-
With algebraic-type, you create an algebraic type like this.
Each key is the action type’s name.
Inside it, you describe the shape of the object.
const Action = AlgebraicType({
AddTodo: {
text: String,
},
DeleteTodo: {
id: Number,
},
EditTodo: {
id: Number,
text: String,
},
CompleteTodo: {
id: Number,
},
CompleteAll: { },
ClearCompleted: { },
})
You still have the benefit of seeing all the actions in your application in a single place.
-
Plain Redux Action Creators: Create an object literal directly.
export function addTodo(text) {
return { type: types.ADD_TODO, text }
}
There is a possibility that you misspelt the property name.
Maybe it’s late at night and you’re hungry and thinking about some tofu soup.
You typed in types.ADD_TOFU
(or just ADD_TOFU
in case of ES6 imports).
You end up dispatching an undefined
action.
You may also be dispatching a malformed object.
-
With algebraic-type
: You invoke the value constructor.
function addTodo(text) {
return Action.AddTodo({ text })
}
The value constructor validates what’s passed into it,
and returns a plain, serializable object with the type
property set to the constructor’s name.
Action.AddTodo({ text: 'Learn Redux' })
You immediately get an error if you misspell it.
Action.AddTofu({ text: 'Learn Redux' })
You immediately get an error if it is not in the shape you specified.
Action.AddTodo({ task: 'Learn Redux' })
algebraic-type
is not a replacement for action creators;
they are simply utilities that helps you creating well-formed action types and action objects.
The case for action creators still holds.
-
Plain Redux: Use switch statements.
If you misspeelt the imported name, your reducer simply won’t process the action.
export default function todos(state = initialState, action) {
switch (action.type) {
case ADD_TODO:
...
case DELETE_TODO:
...
default:
return state
}
}
-
With algebraic-type
: Use the generated matcher function.
If you misspelt the action name, you immediately get a TypeError.
export default function todos(state = initialState, action) {
if (Action.isAddTodo(action)) {
...
}
else if (Action.isDeleteTodo(action)) {
...
}
else {
return state
}
}
-
TK createReducer
example.
These are just ideas; they are not implemented yet.
-
Add name prefix to generated type
to prevent them from clashing.
Maybe follow the ducks convention:
const Action = AlgebraicType({
prefix: 'my-app/widgets/',
Load: { },
Create: { widget: Object },
Update: { widget: Object },
Remove: { widget: Object },
})
Another example is to construct similar-looking actions:
function AsyncAction(prefix) {
return new AlgebraicType({
prefix,
Request: { },
Success: { response: Object },
Failure: { error: String },
})
}
-
Allow type composition/nested actions. This allows actions to be more modular.
Here is an example from Elm’s architecture tutorial, a list of counters:
import { Action as CounterAction } from './counter'
const Action = AlgebraicType({
prefix: 'my-app/main/',
Insert: { },
Remove: { },
Modify: { id: String, action: CounterAction },
})
-
switch() function that takes an incoming object and switches between functions based on type.
-
types() function that returns an list of available keys.