Photo by Miguel Andrade on Unsplash

At Grubhub, catering to restaurants is as important to us as catering to diners. Restaurants have to be able to set their menus, choose their business hours, create promotions, respond to reviews, and of course access their incoming orders. Grubhub For Restaurants (GFR) is the platform we’ve created for this purpose. Originally built in Angular 1.5, we migrated the platform to React/Redux in 2016 and we’ve been very happy with the growth so far.

Redux is the name of a library used in many front-end Javascript applications to manage state. In simpler terms, Redux provides a single “store” object with information about every bit of application state within your web application. That information may be whether or not a navigation menu is open, which food items have been added to an e-commerce shopping cart, or whether an alert message has been triggered. Additionally, it may contain data that was retrieved from an API, such as menu item information retrieved for a specific restaurant. All this data is contained within the Redux store object and is accessible to any part of the web application. Redux also provides a method to modify the application state, by way of what Redux calls actions and reducers. An action is a payload of information that is sent (or, in Redux parlance, “dispatched”) from the application to the Redux store. A reducer is a function that specifies how the application’s state changes in response to actions sent to the store.

One of the challenges with working on complex Redux apps is handling external events, such as server requests, which GFR has a lot of. In an ideal Redux workflow, an action is dispatched and, as a result, the state is changed. Everything is “pure,” or in other words, every action dispatched with a given starting state will always result in the same ending state. In a self-contained app, this works beautifully — events are dispatched, handled, and state is updated synchronously, purely, and with no external dependencies.

In most real-world applications, however, things are not that simple. Apps are dependent on multiple external services, such as talking to the server, recording analytics events, etc, that are not controlled in the scope of the app. Behaviors like this may not occur synchronously and may not always have the same result. Redux has no native way of dealing with these external dependencies, leading to the development of several approaches to handle external events. These events are brought into the pure Redux ecosystem by way of what the Redux community has termed “side effects.”

Several middleware (or “store enhancer”) libraries have been created to support side effects in Redux. One such popular library, for example, is redux-thunk.

“Thunk” is just a fancy way of saying function — instead of the store dispatching an action, it dispatches a function (or “thunk”). The thunk then performs the side effect, such as fetching data from the server, and then dispatches another action when the data is returned.

Thunks, however, have their drawbacks. How do you test the action creator that returns a function without running the side effect itself? Furthermore, “pure” Redux logic gets mixed with side effect logic. This causes the functions to be messy and tricky to maintain.

At Grubhub, we’ve been very happy using the redux-loop library to handle our side effects. Instead of the reducer merely returning the new state, redux-loop changes the reducer signature to return the new state and a description of the side effects to run. The side effects describe what to do and how to handle the result. If you’re familiar with Elm, this is exactly what it does for its side effect management. (Full disclosure, redux-loop is maintained by Bryan Wain, a prominent Grubhub engineer.)

This approach solves several problems inherent in redux-thunk and similar libraries. First of all, the actual logic of handling the side effects is not included in the action creator function. This keeps the reducer simpler while still remaining pure. The side effects are then handled independently by redux-loop. The side effects function(s) are themselves not Redux functions, and have no knowledge of the Redux library itself.

This approach also makes testing very easy. Since the reducer is only returning a description of the side effect, but not actually performing the side effect, it is trivial to have separate logic to test the response from the reducer (namely the new state and the description of the side effect), and to independently test the actual side effect functions.

To show how this is done, let’s walk through an example of where side effects are needed, and how to implement redux-loop to handle the effects.

Getting to the meat of it

In order to demonstrate the power of redux-loop, I’ve created a boilerplate project that uses the create-react-app library to generate random taco recipe ingredients using React, Redux, and redux-loop (and special thanks to Github user @evz for creating an API for getting quality taco recipes). I’m going to assume you have node and yarn already installed on your computer and have a basic understanding of how React and Redux work. To get started, pull down my starting branch:

git clone -b start git@github.com:baruchlane/redux-loop-tacos.git

and run

cd redux-loop-tacos and yarn install && yarn start.

If all goes well, you should see the landing page of the app in your browser. Right now, of course, the app does nothing. We will use redux-loop to handle the fetching of the data from an API and updating the app.

Take a minute to review src/App.js . As you’ll see, the App component uses the react-redux library’s Observer pattern to connect the React component with the Redux store object. This allows Redux dispatches and state changes to be handled within the React component.

Notice that the App component is reading two state properties from the Redux store — the loading flag and ingredients array — and loading them as Redux props.

Considered holistically, the app has three distinct states:

Initial pre-loading state, when the app is first rendered but no data is displayed. The loading state, immediately after the user clicks the button. At this point the API is fetching, but has not returned any data. The loaded state, when the API returns data (or an error, but for the sake of simplicity in this tutorial we will assume that the API call is successful each time). At that point, the App is no longer fetching data but now has taco ingredient data to display.

For each of the states, the combination of props is different. Consider the following props combination for each of the three states:

Pre-loading: { loading: false, ingredients: [] } Loading: { loading: true, ingredients: [] } Loaded: { loading: false, ingredients: ['ingredient1', …] }

So those are the two values — ingredients and loading — that we will be observing, and will update the React app whenever these values change. We have to ensure that the app properly handles each one of these states and transitions correctly from one to the next.

In addition to loading and ingredients, we’re also passing fetchIngredients into the App component as a prop. fetchIngredients is a function that dispatches an action to the Redux store whenever the function is invoked. In our app, we invoke the fetchIngredients function whenever the button is clicked. When invoked, the function dispatches the fetchAction , which is then processed by the reducer in src/reducer.js . Notice that the reducer handles the action by setting loading to true. This change is then reflected in the App component, and the button now shows a loader animation.

Of course, this still doesn’t do anything useful. We’ve transitioned the app into a loading state, but haven’t made any attempt to fetch any data, nor have we determined how to handle the data once it’s fetched. Redux is not equipped to handle asynchronous, unpredictable behaviors (without a store enhancer at least). The API may timeout, it may respond with a 404, or it may return invalid data. How can we kick off an API call from within Redux and asynchronously handle its unpredictable response?

At this point we’re ready to introduce redux-loop and demonstrate how it can solve these problems.

To begin, let’s modify src/redux.js a little bit to include the redux-loop store enhancer into the redux store. Update the file to look like this:

We’ve now added redux-loop as a store enhancer in the redux store and we can harness its power to properly handle side effects, such as fetching from an API.

But before doing that, we should define the actual side effect, namely fetching the taco ingredients data. I’ve actually already done that for you in src/effects.js (shorthand for side effects). This function performs the actual fetch, maps the response into a format that our app can ingest, and returns the data as a Javascript object. It’s also important to note that the function is a completely generic fetch function that has no knowledge of Redux or redux-loop. Finally, let’s import this function into src/reducer.js . For good measure, let’s also import loop and Cmd from redux-loop.

Let’s pause for a minute to discuss loop and Cmd , since the two work hand-in-hand. Cmd is an object that provides helpers to create and represent different kinds of cmd objects. A cmd object is a plain Javascript object that describes the side effect and tells the store how to process it (similar to how calls to React.createElement return objects that describe elements that React will render for you). The beauty of cmds is that they are executed by the redux-loop store enhancer, not the reducer itself, keeping the reducers pure and testable.

loop is a function that takes two arguments, the new, updated state and the Cmd . The updated state represents the temporary state before the side effects are executed.

In our case, we’d like to use loop and Cmd to temporarily update the redux state to set loading: true , and to describe the side effect of fetching the data. Finally, we need to describe to the store what to do when the fetch execution completes and the data is returned.

Let’s begin by redefining the function signature in handleFetch . Replace the function with:

This cmd object is a description of the side effect to perform. It tells the redux-loop store enhancer to execute the asynchronous fetchIngredientsEffect function and dispatch the successAction when the execution is complete. Important to note is that this is just a description of the side effect, but not the execution of the side effect itself. The actual execution of the side-effect is handled in the redux-loop store enhancer, safely out of the reducer.

Finally, notice that I’ve already created a function called handleSuccess , which is invoked by the reducer whenever the successAction action is dispatched. This function updates the Redux state by setting loading back to false and updating ingredients with the actual data retrieved from the API.

At this point, our app is complete. The app initially renders in its preloaded state. Clicking the button places the app into a loading state, and redux-loop executes the side effect of fetching the data asynchronously from the API. The final step is initiated by the redux-loop store enhancer when the fetch is complete. The store enhancer dispatches the successAction action with the returned data, and the App component updates with the delicious ingredients. Try it out in the browser!

Taste testing

But that’s only half the magic. The other part of this is how easy it is to write unit tests for this. Recall that with redux-loop, the reducer does not execute the side effect, it merely describes it. To properly unit test, all we have to do is compare the expected description with the actual description that’s returned from the reducer.

To highlight this, I’ve already created an actual unit test for you, in src/reducer.test.js . All this unit test is doing is comparing the expected loop with the actual loop. It does not actually execute any of the side effects. Separately, we can mock the API call and unit test the fetchIngredientsEffect function by itself, in isolation from the reducer. This keeps the reducer pure and easily testable.

Wrapping it up

And that’s it! To recap, clicking the button dispatches an action that describes how to perform the side effect. The reducer itself does not perform the side effect and remains pure. The redux-loop store enhancer handles the execution of the effect and dispatches the successAction action when the execution completes.

I hope you enjoyed our little taco tutorial of redux-loop. You can find the completed project here on github. And of course, if all this talk of tacos has given you cravings, head over to Grubhub and order yourself a nice carnitas taco on a soft corn tortilla. You’ll thank me afterwards.