In this two-part blog post I would like to show basic and advanced use cases of side-effects management in React applications using Redux-Saga. I will explain why we like it here in AppsFlyer, and what kind of issues it can solve.

This blog will be introductory and will cover some basic concepts related to Redux-Saga, While the second part will be dedicated to challenges Sagas can solve. Note that I assume you have a prior knowledge of React and Redux.

Generators first!

In order to understand sagas, we first need to understand what generators are. According to the docs:

Generators are functions which can be exited and later re-entered. Their context will be saved across re-entrances.”

You can think of a generator function as kind of an iterator, which exposes a ‘next’ method. This method will return the next element in the sequence, or let you know you finished iterating over all the elements in the sequence. This behavior requires the generator to maintain an internal state.

This a basic example of a generator, which returns a couple of strings:

function* namesEmitter() {

yield "William";

yield "Jacob";

return "Daniel";

} // execute the generator

var generator = namesEmitter(); console.log(generator.next()); // prints {value: "William", done: false} console.log(generator.next()); // prints {value: "Jacob", done: false} console.log(generator.next()); // prints {value: "Daniel", done: true}

The generator’s returned value structure is simple - as long as we have values emitted by yield/return, they will appear inside the ‘value’ property. If we don’t have additional values, ‘value’ will be undefined and ‘done’ property will become true.

An important thing to notice is once we execute ‘namesEmitter’, the execution stops when ‘yield’ is called. The execution continues when we call the ‘next’ method of the generator, until the next ‘yield’. Once we use the ‘return’ statement or when a function reaches the end, ‘done’ is positive.

When the sequence length is unknown, we can write the code above like this:

var results = generator.next();

while(!results.done){

console.log(results.value);

results = generator.next();

}

console.log(results.value);

What are sagas?

Sagas are based on generator functions. According to the docs:

“Saga is like a separate thread in your application that’s solely responsible for side effects.”

Imagine a Saga as a thread that constantly calls the ‘next’ method of a generator function and tries to fetch all of the yielded values as fast as possible. You might ask yourself how it’s related to React and why we should even use it, so first let’s see how sagas are connected to React/Redux:

A common flow of React powered by Redux-Saga will start with a dispatched action. If a reducer is assigned to handle this action - the reducer updates the store with the new state and usually the view is being rendered after.

If a Saga is assigned to handle the action - we usually create a side-effect (like a request to the server), and once it’s finished, the Saga dispatches another action for the reducer to handle.

Common use case

We can demonstrate this by showing a common flow:

User interacts with the UI, this interaction triggers a request for data from the server (while displaying a ‘loading’ indication), and finally we use the response value to render something in the page.

Let’s create an action for each step, and see what it looks like with Redux-Saga using a simplified pseudo-code version of the code:

// saga.js

import { take } from 'redux-saga/effects' function* mySaga(){

yield take(USER_INTERACTED_WITH_UI_ACTION);

}

The Saga’s generator function is named ‘mySaga’. It uses a Redux-Saga effect called ‘take’, which is blocking the execution of the Saga until someone dispatches the action given as a parameter. Once ‘USER_INTERACTED_WITH_UI_ACTION’ is dispatched, the method execution will end, just like we saw earlier with the generators (done = true).

Now we will do something in response to this action by causing the UI to render a ‘Loading’ indication. This will be done by dispatching an action for the reducer to handle using a ‘put’ effect which dispatches an action:

// saga.js

import { take, put } from 'redux-saga/effects' function* mySaga(){

yield take(USER_INTERACTED_WITH_UI_ACTION);

yield put(SHOW_LOADING_ACTION, {isLoading: true});

} // reducer.js

...

case SHOW_LOADING_ACTION: (state, isLoading) => {

return Object.assign({}, state, {showLoading: isLoading});

}

...

The next step is executing a request by using the ‘call’ effect, which takes a function and an argument, and executes the function using those arguments. We will give ‘call’ a ‘GET’ function that executes a server call and returns a promise, which will hold the response content when successful:

// saga.js

import { take, put, call } from 'redux-saga/effects' function* mySaga(){

yield take(USER_INTERACTED_WITH_UI_ACTION);

yield put(SHOW_LOADING_ACTION, {isLoading: true}); const data = yield call(GET, 'https://my.server.com/getdata');

yield put(SHOW_DATA_ACTION, {data: data});

} // reducer.js

...

case SHOW_DATA_ACTION: (state, data) => {

return Object.assign({}, state, {data: data, showLoading: false};

}

...

To wrap it up, we dispatch SHOW_DATA_ACTION in order to update the UI with the received data.

What just happened here?

Once the application started, all Sagas are executed, you can think of it like executing the ‘next’ method of a generator function until nothing is left to be yielded. The ‘take’ effect causes something conceptually similar to a thread sleep, which will be resume execution once ‘USER_INTERACTED_WITH_UI_ACTION’ is dispatched.