Before you begin…

This article consists of two parts: first understanding side-effects and how they relate to Redux, and then digging into the fundamentals of Redux-Saga. Feel free to skip to the Redux-Saga section if you are purely interested on jump-starting your understanding of Redux-Saga. But if you are still uncertain about whether Redux-Saga is right for you, then the first part of this article may help you with that decision.

What are side-effects?

Redux is growing evermore popular as the primary method of handling state management in UI web applications. Adopting Redux for a project will often pan out like this (especially for developers using Redux for the first time):

Read and walk through the Redux tutorial Write up a few action creators and reducers for a small part of their application Try to make an AJAX request and store the resulting data in Redux Start searching Stack Overflow to figure out why this simple task can be so confusing Question life choices

What they will have just discovered is that Redux has a very limited notion of what is considered “core” functionality. In a pure Redux application, the application follows this sequence:

An Action is dispatched A Reducer changes the Store Repeat

This application flow is synchronous and deterministic (because reducers are restricted to being synchronous and deterministic). More often than not, this flow is also completely insufficient for handling all of the possible tasks that modern UI web applications perform.

From Redux’s perspective, anything that occurs outside of that normal flow is considered a side-effect, so it’s entirely up to the developer to decide how they should model and implement those tasks, as well as how they should interact with Redux’s barebones application flow. This includes things like:

Interaction with asynchronous APIs

Fetching/posting data via AJAX requests

Setting timeouts and intervals

Dispatching actions in response to other actions

For example, let’s say that we have an API client with a getUser(id) method, which will hit a REST API /users/:id that returns a user profile. We would like to store that data into Redux so that it can be accessed across our application.

Let’s design the actions and state for this. The first instinct might be to create one action, GET_USER , and then have a reducer handle that action. However, user data will be retrieved asynchronously (because it is an AJAX request), so we will follow Redux’s advice on designing asynchronous actions and state:

Now that we have a basic set of action creators, we would like to actually wire them up and make the API request (which is a side-effect to Redux). Let’s explore a few options, using this Redux setup as our base.

How can side-effects be implemented when using Redux?

Since Redux is not opinionated about how side-effects are implemented, many patterns have emerged for managing them. I like to divide these into two categories: patterns which operate outside of the Redux lifecycle entirely (Redux-External), and patterns which interleave with the Redux lifecycle (Redux-Centric).

Redux-External Patterns

The most straightforward approach to implementing side-effects (and the one that most developers will take at first) is to write and trigger them independently of Redux, and have this independent code call out to Redux (via dispatch or getState ).

Redux-External Pattern: View Framework

Assuming that you are using a framework to render or control the application, it will seem natural to have the side-effect implementation exist as a part of that code. For example, if you are using React, a common approach is to trigger a side-effect when mounting a component, and tie the different asynchronous transitions of the side-effect to Redux via bound action creators:

Analogous patterns can be written using other common JS frameworks, by tying Redux into their respective view or service layers. Without a framework, this pattern would look something like having DOM event handlers trigger the side effect code.

Benefits

Can be easier to to understand, by tying side-effect logic to the primary consumer of the side-effect’s results

Doesn’t involve adding new libraries/dependencies

Downsides

Harder to test and reuse code (sometimes; this can be avoided with careful foresight and planning)

Often adds length and complexity to component/controller logic by adding side-effects to previously pure logic

Forces side-effects to be tied to the component lifecycle, rather than running independently

Can’t easily react to events happening in the application flow, such as actions being dispatched

Note: At the time of writing this article, the React Hooks API is still in an alpha stage of development. It looks to be a promising way to make this pattern easier to test and reuse in React, and minimize the added complexity to component code. However, it would still suffer from the downside of having side-effects being tied to the component lifecycle.

Redux-Centric Patterns

Although using a Redux-External pattern is easier to approach, it lacks the ability to trigger more complex side-effects, such as dispatching actions in response to other actions (yes, you could use the subscribe method, but Dan Abramov says you probably shouldn’t). For side-effects like this, we should use a pattern which ties itself into Redux through some sort of middleware.

The following descriptions of Redux-Centric side-effect patterns are largely inspired by Gosha Arinich’s article 3 common approaches to side-effects in Redux apps, and are redescribed here for convenience.

Redux-Centric Pattern #1: Smart Action Creators

In a standard Redux implementation, action creators are pure, meaning they will simply create and return an object, optionally based on some arguments passed to the action creator. In this pattern, we can decide that we want to have action creators also perform the desired side-effects. This is often achieved by having the action creator return something other than a simple object, and using a middleware to handle these non-object actions:

Redux-Thunk is a commonly used implementation of this pattern.

Benefits

Doesn’t require learning any new concepts or mental models outside of standard JavaScript

Downsides

Action creators are no longer pure, making them harder to understand and test

Can often lead to callback hell for anything more than simple side-effects (although async-await can help with this)

Smart action creators can only run when they are called, as opposed to reacting to arbitrary actions

Redux-Centric Pattern #2: Smart Middleware + Specialized Actions

Rather than running side-effects within an action creator, we can move that work into middleware and have our actions provide special instructions for that middleware. This is similar to the Smart Action Creators pattern, except the action creators remain pure, and now a middleware will intercept actions and handle executing side-effects:

Redux-Promise is a commonly used implementation of this pattern.

Benefits

Action creators remain pure, so are easier to test

Downsides

Harder to generalize to handle any desired side-effect; each type side-effect will often require its own specific middleware to handle

Redux-Centric Pattern #3: Redux Hooks/Listeners

We can take the previous pattern and generalize it a step further, by removing any specialization of actions altogether. If we write custom middleware to listen to generic actions as they are dispatched, and perform side-effects independently of the Redux lifecycle, then our action creators and actions will remain simple and pure, and side-effects will be entirely described by that middleware:

In practice, implementations of this pattern rarely have the developer write each middleware directly; they will often create some abstracted model to develop against. Redux-Saga is a commonly used implementation of this pattern.

Benefits

Actions are simple and contain no side-effect business logic

Allows for many listeners to react to a single action

Side-effect logic is contained, and if implemented correctly, easily testable

Downsides

Implementations of this pattern vary greatly, both in complexity and usefulness

Can have a steep learning curve

Which pattern should I choose?

As always, this is going to depend on your project’s needs and goals. These patterns are roughly ordered in terms of how they scale (and unfortunately, in order of complexity as well). For small and simple projects, using the Redux-External approach is often sufficient. As your application grows, and more pieces become interdependent, you may want to switch to a pattern that enables better management of your code.

If you’re still reading this article (glad you’re still here!), then you probably are not satisfied with the pattern you are currently using and are interested in seeing what else is out there. Here at Expanse, we have been utilizing Redux-Saga for our applications, and it has helped us organize our codebase and simplify the way that we implement side-effects. It can be difficult to get off the ground, though; the rest of this article is dedicated to making the learning curve more approachable.