Working on a Redux app of any complexity, you are almost inevitably bound to do one thing at some point: make a server request and save the results.

And the thing is, it can be a little non-obvious as to what to do in a case like this.

What is a side-effect?

The natural Redux flow is this: some action is dispatched, and as a consequence, some state is changed.

If all your Redux app could do was just to change state as the user played with it, though, would your app be particularly useful? Probably not.

Most apps need to reach out to the outside world to be useful — whether by talking to the server, accessing local storage, recording analytics events, or something else entirely.

That process of calling into the real world is what side-effects are. They are a way of bridging the pure Redux world with the outside world.

Common features of side-effects

Side-effects can happen in response to Redux actions. For example, when a user clicks “Save,” you may want to fire off an AJAX request.

Side-effects may dispatch Redux actions. Like when the save process finishes successfully, you may want to dispatch SAVE_SUCCEEDED ; or when it failed, SAVE_FAILED .

They also may not dispatch anything. Some side-effects don’t need to dispatch anything: if you are doing analytics tracking based on Redux actions, you would track things in response to Redux actions, but you will not dispatch anything.

What kinds of approaches are there to handling side-effects?

There are three primary ways of performing side-effects:

(note that code snippets below are purely illustrative)

1. Inside action creators. It is pretty barebones but often is just enough. You make a smart action creator which performs your side-effect and may dispatch an action multiple times.

function fetchUser ( id ) { return dispatch => { dispatch ({ type : 'FETCH_USER' }); fetch ( '...' ). then ( user => { dispatch ({ type : 'FETCHED_USER' }); }, error => { dispatch ({ type : 'ERRORED_USER' }); }); }; }

Props:

simple

no new concepts, no mental overhead

Cons:

action creator is no longer pure

harder to test

can get messy if a certain action involves several side-effects

can only respond to actions, can’t dispatch by itself

spreads logic across dozens of files, interleaving with regular action creators

Example implementations:

2. Have some code on the side respond to user actions. Action creators are still pure, but you now have some piece of code that can listen for a specific action, perform whatever it needs to, and maybe dispatch another action.

function fetchUser ( id ) { return { type : 'FETCH_USER' }; } // in some other place // (pseudo-code) when ( 'FETCH_USER' ). do (( dispatch ) => { fetch ( '...' ). then ( user => { dispatch ({ type : 'FETCHED_USER' }); }, error => { dispatch ({ type : 'ERRORED_USER' }); }); })

Pros:

depending on implementation, can be easier to test

allows having several independent listeners on one action

depending on implementation, can issue actions by itself

allows grouping side-effectful logic

Cons:

several drastically different implementations of the concept

available approaches typically have a learning curve with new concepts

Example implementations:

3. Specialized middleware. Some side-effects are common enough, there exist very specific Redux middlewares. You don’t perform a fetch yourself; you don’t even have a smart action creator. Typically, you’d just dispatch a special action that has the request data, and the middleware takes care of the rest.

function fetchUser ( id ) { return { type : [ "FETCH_USER" , "FETCHED_USER" , "ERRORED_USER" ], url : '...' , }; }

Pros:

keeps the action creator super-simple and pure

Cons:

generally limited to one kind of side-effects — HTTP requests

Example implementations:

Now, there also are other approaches, but these three are the most common.

What should I go with?

There is no universally correct answer to that question.

It always boils down to your needs, requirements, and preferences.

If you are a beginner, smart action creators are the easiest to start with. They are obvious and, to a large extent, there are no surprises.

However, your actions creators will get more verbose, and your logic will get very coupled as your requirements grow.

Specialized middlewares can be an okay replacement for smart action creators if your logic is very simple. They also can be used in conjunction with the “code on the side” approach.

“Code on the side” techniques are more flexible because they allow you to listen to specific actions and react accordingly. Several pieces of code can react to the same action — and they don’t have to know about each other, the action creator doesn’t have to know about them, too.

For example, when SAVE is dispatched, you may want to send a server request, but you may also decide to track this event to analytics. You can have an analytics file which listens to various Redux actions and tracks things. And at the same time, you can have an API file which listens to various Redux actions and sends requests. And the two are not coupled in any way.

Try one implementation from every category to understand them in a practical setting, and to see how you feel about these.