Structuring your Redux application correctly the first time can be difficult -- especially if you don't fully understand the role actions play in the system.
The official Redux.js documentation uses examples like addTodo() and requestPosts().
What's wrong with the examples?
Adding a Todo
This snippet from the Redux documentation is the action creator for adding a todo to a list.
let nextTodoId = 0export const addTodo = (text) => { return { type: 'ADD_TODO', id: nextTodoId++, text }}
In the context of the TodoMVC example application, the ADD_TODO action type and the addTodo() action creator are adequately named because the action tells a reducer to add a todo. We'll see later, however, how this naming doesn't work when adding a todo is an asynchronous operation.
Request Posts
This snippet from the Redux documentation is the action creator for requesting posts from a subreddit.
function requestPosts(subreddit) { return { type: REQUEST_POSTS, subreddit }}
The REQUEST_POSTS action type and requestPosts() action creator are poorly named because the action is not telling a reducer to request posts. Take a look at the associated reducer:
function posts(state /*...*/, action) { switch (action.type) { /* ... */ case REQUEST_POSTS: return Object.assign({}, state, { isFetching: true, didInvalidate: false }) /* ... */ }}
The action is simply telling the reducer that something is happening (i.e. we're fetching something and that it hasn't been invalidated yet). This naming might, at best, confuse a newcomer and, at worst, mislead someone to put side effects in their reducer (e.g. perform an API call from inside the reducer).
A Simple Naming Convention
The English language has two similar words, affect and effect.
- Effect is most commonly a noun that means the result of an action
- Affect is most commonly a verb that means to make a difference
(for a full explanation, see here: Affect vs. Effect )
Naming Action Types
When dealing with asynchronous web applications, your actions should generally describe effects. That is, your action should describe a change that has happened in your system. For example, a user logged on, some data was loaded, an API call was started, an error was encountered, etc.
You then dispatch the action to your store, whose job it is to reconcile the change with the entire application state.
Avoid REQUEST_POSTS and RECEIVE_POSTS because they suggest they affect the system. Going with REQUESTING_POSTS and RECEIVED_POSTS is better but I don't like that particular pairing because I can't think of a good name for a companion action type that indicates failure.
The preferred naming should group similar action types together.
const Actions = { LOAD_POSTS_REQUEST: 'LOAD_POSTS_REQUEST', LOAD_POSTS_FAILURE: 'LOAD_POSTS_FAILURE', LOAD_POSTS_SUCCESS: 'LOAD_POSTS_SUCCESS',};
Naming Simple Action Creators
Simple action creators are most analogous to effects and should be named accordingly. By simple action creators I mean functions that produce a single Redux action to be consumed directly by the store. For example,
const loadPostsRequest = subreddit => ({ type: Actions.LOAD_POSTS_REQUEST, payload: subreddit});const loadPostsSuccess = posts => ({ type: Actions.LOAD_POSTS_SUCCESS, payload: posts});const loadPostsFailure = err => ({ type: Actions.LOAD_POSTS_FAILURE, payload: err, error: true});
Naming Complex Action Creators
On the other hand, some action creators produce actions indirectly through Redux middleware. These are what I call complex action creators.
Complex action creators (created by redux-thunk, redux-promise, etc) are more likely to be the agents of change in your system -- i.e. they affect your state. These should be named with present-tense verbs. For example, loadPosts() is a thunk that changes state.
const loadPosts = subreddit => dispatch => { // Tell Redux we're now loading... dispatch(loadPostsRequest(subreddit)); fetch(subreddit).then(posts => // Tell Redux we succeeded dispatch(loadPostsSuccess(posts)); }, err => { // Tell Redux we failed dispatch(loadPostsFailure(err)); });};
Adding Todos... Asynchronously
What if the TodoMVC example was sending new todos to a server and needed to be acknowledged by the server before being added locally? Then ADD_TODO/addTodo() is insufficient. Instead, you may want to consider { TODO_ADD_REQUESTED, TODO_ADD_SUCCEEDED, and TODO_ADD_FAILED }.
Or maybe { ADD_TODO_REQUESTED, ADD_TODO_SUCCEEDED, and ADD_TODO_FAILED } depending on what sounds more natural to you and whether you want to group things by noun or by verb.
Recap
Name your action types and creators in a way that is intuitive / non-misleading and -- most importantly -- be consistent.
You Might Also Like...
- Starting a React/Redux Project with TypeScript
- Redux Hero Part 1: A Fun Introduction to Redux.js
- 5 Strategies to Prevent Bugs in a Large Redux Codebase