Redux FAQ: Actions
Table of Contents
- Redux FAQ: Actions
- Table of Contents
- Why should
typebe a string, or at least serializable? Why should my action types be constants?
- Is there always a one-to-one mapping between reducers and actions?
- How can I represent “side effects” such as AJAX calls? Why do we need things like “action creators”, “thunks”, and “middleware” to do async behavior?
- What async middleware should I use? How do you decide between thunks, sagas, observables, or something else?
- Should I dispatch multiple actions in a row from one action creator?
- Why should
type be a string, or at least serializable? Why should my action types be constants?
As with state, serializable actions enable several of Redux's defining features, such as time travel debugging, and recording and replaying actions. Using something like a
Symbol for the
type value or using
instanceof checks for actions themselves would break that. Strings are serializable and easily self-descriptive, and so are a better choice. Note that it is okay to use Symbols, Promises, or other non-serializable values in an action if the action is intended for use by middleware. Actions only need to be serializable by the time they actually reach the store and are passed to the reducers.
We can't reliably enforce serializable actions for performance reasons, so Redux only checks that every action is a plain object, and that the
type is defined. The rest is up to you, but you might find that keeping everything serializable helps debug and reproduce issues.
Encapsulating and centralizing commonly used pieces of code is a key concept in programming. While it is certainly possible to manually create action objects everywhere, and write each
type value by hand, defining reusable constants makes maintaining code easier. If you put constants in a separate file, you can check your
import statements against typos so you can't accidentally use the wrong string.
- #384: Recommend that Action constants be named in the past tense
- #628: Solution for simple action creation with less boilerplate
- #1024: Proposal: Declarative reducers
- #1167: Reducer without switch
- Stack Overflow: Why do you need 'Actions' as data in Redux?
- Stack Overflow: What is the point of the constants in Redux?
Is there always a one-to-one mapping between reducers and actions?
No. We suggest you write independent small reducer functions that are each responsible for updates to a specific slice of state. We call this pattern “reducer composition”. A given action could be handled by all, some, or none of them. This keeps components decoupled from the actual data changes, as one action may affect different parts of the state tree, and there is no need for the component to be aware of this. Some users do choose to bind them more tightly together, such as the “ducks” file structure, but there is definitely no one-to-one mapping by default, and you should break out of such a paradigm any time you feel you want to handle an action in many reducers.
- Twitter: most common Redux misconception
- #1167: Reducer without switch
- Reduxible #8: Reducers and action creators aren't a one-to-one mapping
- Stack Overflow: Can I dispatch multiple actions without Redux Thunk middleware?
This is a long and complex topic, with a wide variety of opinions on how code should be organized and what approaches should be used.
Any meaningful web app needs to execute complex logic, usually including asynchronous work such as making AJAX requests. That code is no longer purely a function of its inputs, and the interactions with the outside world are known as “side effects”
Redux is inspired by functional programming, and out of the box, has no place for side effects to be executed. In particular, reducer functions must always be pure functions of
(state, action) => newState. However, Redux's middleware makes it possible to intercept dispatched actions and add additional complex behavior around them, including side effects.
In general, Redux suggests that code with side effects should be part of the action creation process. While that logic can be performed inside of a UI component, it generally makes sense to extract that logic into a reusable function so that the same logic can be called from multiple places—in other words, an action creator function.
The simplest and most common way to do this is to add the Redux Thunk middleware that lets you write action creators with more complex and asynchronous logic. Another widely-used method is Redux Saga which lets you write more synchronous-looking code using generators, and can act like “background threads” or “daemons” in a Redux app. Yet another approach is Redux Loop, which inverts the process by allowing your reducers to declare side effects in response to state changes and have them executed separately. Beyond that, there are many other community-developed libraries and ideas, each with their own take on how side effects should be managed.
- Redux Side-Effects and You
- Pure functionality and side effects in Redux
- From Flux to Redux: Async Actions the easy way
- React/Redux Links: "Redux Side Effects" category
- Gist: Redux-Thunk examples
- #291: Trying to put API calls in the right place
- #455: Modeling side effects
- #533: Simpler introduction to async action creators
- #569: Proposal: API for explicit side effects
- #1139: An alternative side effect model based on generators and sagas
- Stack Overflow: Why do we need middleware for async flow in Redux?
- Stack Overflow: How to dispatch a Redux action with a timeout?
- Stack Overflow: Where should I put synchronous side effects linked to actions in redux?
- Stack Overflow: How to handle complex side-effects in Redux?
- Stack Overflow: How to unit test async Redux actions to mock ajax response
- Stack Overflow: How to fire AJAX calls in response to the state changes with Redux?
- Reddit: Help performing Async API calls with Redux-Promise Middleware.
- Twitter: possible comparison between sagas, loops, and other approaches
There are many async/side effect middlewares available, but the most commonly used ones are
redux-observable. These are different tools, with different strengths, weaknesses, and use cases.
As a general rule of thumb:
- Thunks are best for complex synchronous logic (especially code that needs access to the entire Redux store state), and simple async logic (like basic AJAX calls). With the use of
async/await, it can be reasonable to use thunks for some more complex promise-based logic as well.
- Sagas are best for complex async logic and decoupled "background thread"-type behavior, especially if you need to listen to dispatched actions (which is something that can't be done with thunks). They require familiarity with ES6 generator functions and
redux-saga's "effects" operators.
- Observables solve the same problems as sagas, but rely on RxJS to implement async behavior. They require familiarity with the RxJS API.
We recommend that most Redux users should start with thunks, and then add an additional side effect library like sagas or observables later if their app really requires handling for more complex async logic.
Since sagas and observables have the same use case, an application would normally use one or the other, but not both. However, note that it's absolutely fine to use both thunks and either sagas or observables together, because they solve different problems.
- Decembersoft: What is the right way to do asynchronous operations in Redux?
- Decembersoft: Redux-Thunk vs Redux-Saga
- Redux-Thunk vs Redux-Saga: an overview
- Redux-Saga V.S. Redux-Observable
- Reddit: discussion of using thunks and sagas together, and pros and cons of sagas
- Stack Overflow: Pros/cons of using redux-saga with ES6 generators vs redux-thunk with ES2017 async/await
- Stack Overflow: Why use Redux-Observable over Redux-Saga?
Should I dispatch multiple actions in a row from one action creator?
There's no specific rule for how you should structure your actions. Using an async middleware like Redux Thunk certainly enables scenarios such as dispatching multiple distinct but related actions in a row, dispatching actions to represent progression of an AJAX request, dispatching actions conditionally based on state, or even dispatching an action and checking the updated state immediately afterwards.
In general, ask if these actions are related but independent, or should actually be represented as one action. Do what makes sense for your own situation but try to balance the readability of reducers with readability of the action log. For example, an action that includes the whole new state tree would make your reducer a one-liner, but the downside is now you have no history of why the changes are happening, so debugging gets really difficult. On the other hand, if you emit actions in a loop to keep them granular, it's a sign that you might want to introduce a new action type that is handled in a different way.
Try to avoid dispatching several times synchronously in a row in the places where you're concerned about performance. There are a number of addons and approaches that can batch up dispatches as well.
- #597: Valid to dispatch multiple actions from an event handler?
- #959: Multiple actions one dispatch?
- Stack Overflow: Should I use one or several action types to represent this async action?
- Stack Overflow: Do events and actions have a 1:1 relationship in Redux?
- Stack Overflow: Should actions be handled by reducers to related actions or generated by action creators themselves?
- Twitter: "Good thread on the problems with Redux Thunk..."