August 20, 2018

What the React? Sagas and side effects

Emre Sonmez

Software Engineer

Our first rendition of “What the React?” has arrived! Today, we’re going to dive into how we handle side effects in our React apps at Smartcar.

When we first started designing our developer dashboard and aligned on using Redux for state management, we faced the question many developers face when building React apps: where do we put the side effects?

Seriously, where do we put the side effects?!

What are side effects?

Side effects are basically anything that affects something outside of the scope of the current function that’s being executed. In our dashboard, this includes:

  • API requests to our backend service
  • Calls to our authentication service
  • Error tracking calls to Sentry

In the context of our app, this looks like:

  1. Developer clicks “Register application.”
  2. We dispatch a REGISTER_APPLICATION event.
  3. We make a call to our backend service to register the application. (side effect!)
  4. We extract the data returned from the backend call and dispatch a REGISTER_APPLICATION_SUCCESS event with this data. (side effect!)
  5. We route to this new registered application. (side effect!)

Where should we group side effects?

Well, not in reducers.

Reducers — functions that we use to update our app’s state given its current state and an action — are pure functions. They cannot contain side effects like making API calls to our backend service.

So, where do we put the three side effects listed above when a REGISTER_APPLICATION event is dispatched to our Redux store?

When thinking about side effects in React apps, we thought about the following constraints:

  • We can’t handle side effects in reducers (they’re pure functions, they aren’t meant for making API calls).
  • We want something that’s easy to test (Jest and Enzyme should make this easy). Less mocking the better.
  • We want side effects to be closely tied to Redux actions.

We looked around for what was popular with the React community and found two options that could hit our criteria: Thunks and Sagas.

Option 1: Thunks

The first option we looked into was Thunks. Thunks transform action creators so that they can return functions (rather than JSON objects representing actions). We can then use these functions to make API requests and trigger dispatch calls to our redux store.

The benefit of using Thunks is that they are lightweight and easy to integrate into a React-Redux application. However, Thunks require us to mock our APIs to test.

This complicates testing — and makes it a lot less fun.

Option 2: Sagas

Now for Sagas!

There are a few things that make Sagas different. Most importantly, they’re heavier than Thunks (we add a middleware layer to our React-Redux app); but, they also have a whole suite of benefits that make it incredibly easy to manage side effects in our React apps.

Here are a few of the benefits:

Sagas are generator functions.

Sagas are generator functions that listen for Redux actions. When an action is dispatched to the redux store, a corresponding Saga will execute. This allows us to pair a Saga with a particular redux action.

Sagas yield plain JavaScript objects, called Effects.

What made Sagas delightful for us to use was its concept of Effects. An Effect contains an instruction for the Saga middleware but does not perform any execution.

Inside a Saga, we yield plain JavaScript objects (Effects). This eliminates the need for us to mock functions to test Redux Sagas (which leads to better tests!).

We use Effect creators (provided by the Redux-Saga API), functions that create these JavaScript objects, to generate our Effects. Some of the ones we use at Smartcar include:

  • call(fn, …args) — creates an Effect that instructs the middleware to call the specified function with the specified args
  • put(action) — creates an Effect that instructs the middleware to dispatch a Redux action to the store
  • select(selector, …args) — creates an Effect that instructs the middleware to invoke the selector on the current Redux store state with the specified args

When the middleware retrieves an Effect, the Saga is paused until the event is fulfilled.

Sagas are easy to test.

Using Effects inside Sagas make them incredibly easy to test. A Saga function does not make any external API calls — it yields Effects which are executed by the Saga middleware. Since these Effects are plain JavaScript objects, we can test Sagas by making sure a task yields a call with the right function and the right arguments.

An example

This is what a Saga to register an application looks like:

As you can see, we’ve placed all side effects for the REGISTER_APPLICATION action into a single place! And now for the test:

As we noted earlier, we don’t have to mock any of our API calls — in each line, we just check if the Effect object matches what we expect it to be.

So, to sum up what we’ve learned:

  1. Sagas are heavier than Thunks, but much easier to test.
  2. We ❤️ Sagas at Smartcar.

We’ll see you next time 😉

Smartcar is the connected car API that allows mobile and web apps to communicate with connected vehicles across brands (think “check odometer” or “lock doors”).

Want to take our API for a spin? Check out our docs and demo app.

Everything you need to know about car APIs. Delivered monthly.

Thank you! Your submission has been received!
Oops! Something went wrong while submitting the form.