Actions are objects that are dispatched and tell reducers what to do. They contain a type
- usually a string, and some payload
- usually an object.
{
type: 'DO_SOMETHING',
payload: {
// some data
}
}
Actions are payloads of information that send data from your application to your store. They are the only source of information for the store. You send them to the store using
store.dispatch()
.
Objects containing information that are dispatched to the reducers, which in turn update the store.
1// An example Action object
2{
3 type: 'ADD_TODO', // 'type' is required
4}
1// An example Action object with a TYPE and PAYLOAD (i.e. data being sent to reducer, but we aren't calling it 'payload'. it'll still work..)
2{
3 type: 'ADD_TODO',
4 text: 'Build my first Redux app'
5}
1// An example Action object with a TYPE and PAYLOAD (i.e. data being sent to reducer)
2{
3 type: 'ADD_TODO',
4 payload: {
5 text: 'Build my first Redux app',
6 status: 'In Progress'
7 }
8}
1// An example Action object with a TYPE (constant) and PAYLOAD (i.e. data being sent to reducer)
2{
3 type: ADD_TODO,
4 payload: {
5 text: 'Build my first Redux app',
6 status: 'In Progress'
7 }
8}
1// Dispatch an Action (inline)
2dispatch({
3 type: "ADD_TODO",
4 payload: {
5 text: "Build my first Redux app",
6 status: "In Progress"
7 }
8});
1// Dispatch an Action (saved as an Action Creator function)
2dispatch(addTodo("Build my first Redux app"));
Vocabulary | Type |
---|---|
Action | Object |
Action type | String recommended, could be anything serializable |
Action (type) constant | type saved as const |
Action payload | Object usually, could be anything |
Action creator | Function |
Action: {
type: String,
payload: String/Object/Array/whatever
}
On an Action object:
type
is required. Every action must have a type. We use this type in our reducers to determine what changes to make for what type of action. Calling it type is convention in Redux, and a hard requirement in @reduxjs/toolkit
, where you can reference action.type
for a function created as a result of createAction()
payload
is optional. It is the information being sent to the reducer, could be anything (sring, object, array whatever). Calling it payload is convention, you can call it whatever you want and reference it in your reducer by that name, but sticking to convention is good.Every action must have a type
property specifying what type of an action is being performed. The value should be a String
(recommended).
1// An example Action object
2{
3 type: 'ADD_TODO',
4}
the action type strings saved as constants.
1// Action constants (saving strings as const to avoid typos)
2const ADD_TODO = "ADD_TODO";
1// Combining action constants into one file and importing/exporting the relevant ones
2import { ADD_TODO, REMOVE_TODO } from "../actionTypes";
1// An example Action object with a TYPE and PAYLOAD (i.e. data being sent to reducer)
2{
3 type: ADD_TODO,
4 text: 'Build my first Redux app'
5}
to avoid typos, to gather them all in one place and to easily import/export them
Functions generating action objects (instead of writing action objects directly in the code at the time of dispatch
).
1function addTodo(text) {
2 return {
3 type: ADD_TODO,
4 text
5 };
6}
1// Dispatch an Action (saved as an Action Creator function)
2dispatch(addTodo("Build my first Redux app"));
Instead of creating the objects inline at the time of dispatch, you can save them before hand. The major benefit of doing that is that when you need to change an action object later, you’d only do it in one place instead of making inline changes where you dispatched that particular action
an function that also includes dispatch()
alongwith the action creator, i.e. it is bound to automatically dispatch when called:
1const boundAddTodo = text => dispatch(addTodo(text));
2const boundCompleteTodo = index => dispatch(completeTodo(index));
bindActionCreators()
is there to automatically bind many action creators to a dispatch()
function. Redux only.
From the recommended and opinionated @reduxjs/toolkit
, createAction()
combines the process of creating an action type (constant) and an action creator (function) into a single step.
Funnily enough, both action constants and action creators are listed under reducing boilerplate, when in fact they are responsible for adding more lines of code and more files and folders.. The toolkit’s createAction()
is the one that actually reduces code and complexity and makes Ducks (actions, reducers for one feature in one file) possible.
createAction()
takes an action type as string and returns an action creator function for that action type. In other words, it creates an Action creator by taking an action type. Generated function takes a single argument that becomes action.payload
. If you want to customize that payload, you can pass a second argument, i.e. the prepare()
function to createAction()
.
1import v4 from "uuid/v4";
2
3const addTodo = createAction("todos/add", function prepare(text) {
4 // the prepare() is optional, for when you need to customize the payload
5 return {
6 payload: {
7 text,
8 id: v4(),
9 createdAt: new Date().toISOString()
10 }
11 };
12});
Notice that in this example we’re also using uuid
and Date()
to add id
and createdAt
values to our payload. Following is the resulting Action object that will be created:
1console.log(addTodo("Write more docs"));
1{
2 type: 'todos/add',
3 payload: {
4 text: 'Write more docs',
5 id: '1b9d6bcd-bbfd-4b2d-9b5d-ab8dfbbd4bed',
6 createdAt: '2019-10-03T07:53:36.581Z'
7 }
8}
If you don’t provide a prepare()
, the single parameter passed to the generated action creator, (addTodo()
in the example above) will become action.payload
. For example:
1const increment = createAction("counter/increment");
2
3let action = increment(); // no arg, no payload
4// { type: 'counter/increment' }
5
6action = increment(3); // single parameter = action.payload
7// returns { type: 'counter/increment', payload: 3 }
You can use toString()
or type
to reference the action type in a reducer. For example: addTodo.type
1const increment = createAction("INCREMENT");
2const decrement = createAction("DECREMENT");
3
4function counter(state = 0, action) {
5 switch (action.type) {
6 case increment.type:
7 return state + 1;
8 case decrement.type:
9 return state - 1;
10 default:
11 return state;
12 }
13}
createAction() API | Returns |
---|---|
foo.type | Action type (string) |
foo.toString() | Action type (string) |
foo.payload | Action payload (could be anything) |
foo.match() | determine if the passed action is of the same type as an action that would be created by the action creator |
foo
is the generated action creator..
A bit out of scope for this article, but i wanted to show where and how the type and payload will be used:
This is the Action we are dispatching (basic form, foregoing action constants and action creators at the moment)
1// Dispatch an Action (inline)
2dispatch({
3 type: "ADD_TODO",
4 payload: {
5 text: "Build my first Redux app",
6 status: "In Progress"
7 }
8});
And this is how we are using that dispatched information in the Reducer to update our Store
1// An example Reducer function responsible for updating state
2function todoApp(state = [], action) {
3 switch (
4 action.type // applying changes based on action 'type'
5 ) {
6 case ADD_TODO:
7 return [
8 ...state,
9 {
10 text: action.payload.text, // using details from 'payload' that the action sent
11 status: action.payload.status
12 }
13 ];
14
15 default:
16 return state;
17 }
18}
When using createAction()
you will use addTodo.type
instead of ADD_TODO
in your CASE
statements