Why doesn't Redux support AJAX out of the box?

28 Sep 2016

But how the hell do I make remote requests with Redux?

— every newcomer, ever

Wow, you know quite a bit of React already; Redux too. You’re at the point where your little project needs to start talking to the outside world.

Now… you feel a bit unsure where would this fit. Should it go into a reducer? or?..

How should it even work???

Recap of Redux

What is Redux, essentially?

It says in the readme

Redux is a predictable state container

and while the claim is true, there’s more to it than that.

The cornerstone of Redux is an event log. All kinds of things that happen in the app are recorded as facts.

A typical interaction with a todo app can be represented with events like this:

ADD_TODO { text = "Do groceries" }
ADD_TODO { text = "Call Stan" }
COMPLETE_TODO { text = "Do groceries" }
ADD_TODO { text = "Wash dishes" }

The state is a different view on actions, it helps the application make sense of the past events. Reducers are a piece that updates the state based on actions that have happened.

You know all of that already! I just wanted to emphasize it one more time.

So how it all relates to AJAX, again?

Easy.

Suppose we have an app which has one button called “Random image” When it’s clicked, we reach out to the server, get a random image and render it.

Putting some thought into formalizing the task at hand can be helpful. It can feel pointless and time-consuming at first, but it helps you get a clear picture.

Let’s try to think what happens there in terms of events.

LOAD_RANDOM_IMAGE for sure!

LOAD_RANDOM_IMAGE
??? - what happens there?

Is that it? Nope. What else would be a cold hard fact there?

The response we got from the server! It happened, we need it, so it’s definitely something we’ll want to record.

So need two events, one to represent a button click, another to receive the image (don’t stress out much about how it happens yet):

LOAD_RANDOM_IMAGE
RECEIVE_RANDOM_IMAGE { url = "http://example.com/cat-samba.gif" }

Tying it together

Awesome! Now we got our domain events straight.

There’s still one piece of the puzzle missing. How do requests happen?

Normally, what goes into mapDispatchToProps is the dispatching of a single action:

function mapDispatchToProps(dispatch) {
  return {
    loadRandomImage() {
      dispatch(actions.loadRandomImage());
    }
  };
}

But in this case, we clearly need more. We need to dispatch one action immediately and dispatch another one when we receive the response from the server.

How do we do it? Easy.

function mapDispatchToProps(dispatch) {
  return {
    loadRandomImage() {
      dispatch(actions.loadRandomImage());
      api.fetchRandomImage().then(image => {
        dispatch(actions.receiveRandomImage(image));
      });
    }
  };
}

Note that we are agnostic of how the API requests happen: with fetch, $.ajax, or plain ol’ XMLHttpRequest. That is abstracted away in an api module. All we care about is, it returns a promise.

Now, having that right inside mapDispatchToProps can feel a bit dirty, let’s move that to a separate function. One that takes dispatch.

function loadRandomImage(dispatch) {
  dispatch(actions.loadRandomImage());
  api.fetchRandomImage().then(image => {
    dispatch(actions.receiveRandomImage(image));
  });
}

function mapDispatchToProps(dispatch) {
  return {
    loadRandomImage() {
      loadRandomImage(dispatch);
    }
  };
}

Where does this function belong? Wherever you see fit. A couple of places it’s usually placed:

Whichever works for you is fine, really. Don’t buy on the “it HAS to be in X,” try all of those and see which works better for you.

Back to the original question

— So why doesn’t Redux ship with AJAX support?

— Because AJAX is not a weird cousin that need special support. AJAX fits in naturally into the Redux model.

Bonus: thunks

You may have noticed that with regular actions, it’s usually dispatch(action()), but what are doing is action(dispatch).

There is nothing with this approach — after all, we need to dispatch several actions, not one, so it makes sense.

However, if this feels a bit out-of-place for your taste, you can try Redux-Thunk, which allows you to go back to dispatch(action()), by making dispatch accept a function.

In our case, after applying redux-thunk, mapDispatchToProps could be this:

function mapDispatchToProps(dispatch) {
  return {
    loadRandomImage() {
      dispatch(loadRandomImage);
    }
  };
}

This way the components don’t care whether an action is smart or not.


I’ve also written about two Redux anti-patterns:

Want to level up your React skills?

Sign up below and I'll send you content just like this about React straight to your inbox every week.

No spam, promise. I hate it as much as you do!

, enjoying the article? Now think of 3 friends who are interested in React, Redux and would be into it, and share the link with them! 👇

http://goshakkk.name/redux-no-ajax-by-default/