Architect with Sagas Part II

The Saga Continues — Magic in React

No broomstick required. Just a few minutes and a bit of patience.

To get a good idea of how flex works try flex layout editor on this page.

On July 25, 2017 I wrote a brief article about Saga architecture in React. It included a Saga-React integration diagram explaining the basic relationship between asynchronous events and dispatching actions to the API.

So what is the Saga Pattern in React and what does it look like?

React Saga Pattern ‒ a bird’s eye view.

I’ll just put this forward right now — sagas aren’t the easiest thing I had to learn in my whole career as a software engineer. I cannot possibly include everything in this tutorial. But I’ll try to do my best and hope to succeed in at least pointing you in the right direction.

So you finally got Redux under your belt & it already took some time to figure it out. So why then take the next step and bother learning the saga pattern?

Well, there is one pretty good reason for it.

The Saga Pattern Can Significantly Reduce Development Time

The rewards of an accurately set up saga pattern cannot be easily underestimated. Once you got a grasp on this… it will be hard to go back due to significant decrease in development time.

I learned of Sagas while working on building 18-wheeler axle mapping application for the Goodyear tire company. The app was multi-dimensional. Meaning, it had about 15 different UI views.

Some UIs were more complex than others. Using sagas has certainly improved how fast it was possible to build them out.

One of the views — a fairly complex truck axle editor — is depicted below.

18 wheeler axle layout editor UI view.

Note, this is an early version of the React UI that was actually built for the Goodyear co. I can’t show you the final product.

It took about 5 days total to create in React together with all the API saga calls!

Of course then it took several more days to polish and fix minor bugs. But without saga pattern it would have taken a whole lot longer.

Learning the Saga Pattern

And now I am here to tell the story and share my experience with those who want to learn how to create their own sagas.

What in the world is a saga?

In order to find out we need to write complete source code and craft our own saga pattern from scratch. And that is exactly what I hope to accomplish with this article.

Get a Diet Coke and some snacks, because this will take a while to sink in. We’ll start with theory and at the end of this article I’ll present the complete source code. So let’s dive right in!

Sagas live and breathe on Redux. You will need to make sure you used NPM to install the redux package. But what is Redux? We won’t go deep into detail here but basically here is what Redux is:

Redux Pattern in React.

Saga is a type of a Redux pattern providing asynchronous event dispatch. It takes the store concept and adds API calls into the mix without breaking the redux pattern. You can think of sagas as “redux for making HTTP requests.”

What are sagas for? It is basically Redux with asynchronous actions. In short… once you set up the pattern it’ll solve a few API-related problems associated with application architecture in a somewhat elegant way.

What do sagas do? In Redux pattern you pass props down your UI component hierarchy. This prop can be a function coming from a parent component that executes via an onClick event. This onClick may execute a parent’s component’s method. And in this method you might dispatch an action.

This action executes. And then returns a result. This result is passed to a reducer. Reducer maps the returned data and updates the state of your application. And then…the cycle starts all over again.

Sagas can be based on async using yield keyword so they can help you dispatch events asynchronously without having to wait until data is returned. But soon as it does… coupled with a reducer the application state will be updated. It’s pretty straightforward. But takes a bit of time to set up.

Together with Node… JavaScript is like a Swiss Army Knife. Before we dive in deep let’s cut through the basics and briefly review what it takes to implement sagas from scratch to avoid any confusion later down the road.

The yield keyword is used to pause and resume a generator function. It is usually followed by either call, put or fork keywords. Let’s go over them.

Fetching and reducing return values in Saga is a two fold process: The yield call executes the API call. And yield put takes the fetched data and sends it to your reducer. The reducer then updates the state.

The main Saga list containing all your events is created using yield fork.

The fork keyword imported from redux-saga/effects NPM package helps you create attached forks. A fork is a task that executes in the background.

So make sure you have redux-saga and redux-saga-effects NPM-installed.

These NPM plugins also provide spawn keyword which creates the so called detached fork. Detached forks live in their own execution context. But spawn will not be used in this tutorial. I just wanted to put it out there as a side note.

A yield fork can and should contain an array of all possible actions. As in:

app-root/sagas/index.js

import { usersGetList, usersSetName, otherOnes } from “./users”;
export function *sagas() {
yield
[
fork( takeLatest, “GET_USER_LIST”, usersGetList ),
fork( takeLatest, ”SET_USER_NAME”, usersSetName ),
fork( ...
      /* ...Add more crazy actions here... go wild... */
    ]}

…and so forth.

Note that takeLatest is magic imported from redux-saga package. What I mean is that it abstracts the fork process. Fully understanding it will depend on how deep you want to go into learning about sagas.

We will use takeLatest just once to add all API calls to our global saga list. But for this tutorial I don’t want to break below this level of abstraction. If you want to learn more about forking and saga patterns you can head over to the Saga API page.

At its basic takeLatest will spawn a new saga on each dispatched action. It will be sent to the Store that matches the pattern provided. It will also cancel any previous saga tasks started if they are still running.

The master saga list app-root/saga/index.js needs to import all saga methods for each individual component. Here <users.js> contains all saga methods specifically for the user component. It’s imported as:

import {usersGetList, usersSetName} from “users”;

You probably already know that users is just shorthand for users.js – because you can skip <.js> part when importing JavaScript files.

Expect to import all individual saga methods from each one of these files.

They should live under app-root/sagas/ directory. Usually in addition to master list app-root/sagas/index.js there is one additional saga file for each component like: /users.js, /alerts.js, /tags.js, etc.

What about the fork keyword?

This yield fork array will determine our master Saga event list. We’ll see it again once we get to the source code part later in this tutorial. Together with takeLatest the fork will create and dispatch a new saga.

To summarize what we have so far…

The index.js file is the complete list of all Saga events for your application. Whenever you need to create a new action for adding, updating or deleting something from the database just add one to this list there and import the components that actually define methods associated with those events.

Basically the master Saga list is your global CRUD factory.

Anatomy of an Action

Actions are really simple. They are passed to the dispatch method as a string and some payload argument. Later in this tutorial you will see exactly how this is done.

An action has a name and payload. The name is whatever you want to call it in context of your application.

For example: GET_USER_LIST. And the payload is the received user list as a JSON object. It is also often referred to as a resource. In situations where you don’t need to send data (delete user for example) there is no payload.

But otherwise… actions are HTTP requests that write to or pull some data from API. An action can trigger this request and wait asynchronously for the saga to return. Meanwhile as this is happening the user is free to continue using your application even if the request hasn’t returned yet.

In other words data communication layout of your application will not freeze UI rendering when using the saga redux pattern. Moreover when the return value of your operation is passed on to the reducer it automagically updates the state of your application as soon as possible.

Sagas are magic. No broomstick or cauldron required. Just a keyboard and a pair of hands. And a basic mammalian brain.

With this background intel we’re ready to start crafting our first sagas!

7 Steps For Setting Up Saga Architecture

The saga pattern encompasses many principles. You’re assumed to know what Flux is. Redux, dispatch, generators, async, props propagation from parent to child and events bubbling up to change state…

Also, I hope you’ve heard of mapStateToProps function that ties it all together. If not, don’t worry, we’ll take a look at its use in this tutorial!

Quick side note: You don’t really need to fully understand all of these principles, keywords, methodologies and so on to write working code. But to write great code you might as well try. There shouldn’t be a reason for not trying to learn what that means at any stage in your career.

Don’t let lack of knowledge discourage you from actually writing code. A lot of the time this is how you will learn.

You can simply put together a saga architecture by following this tutorial as though each step was a Lego block. Or something. React — or any other type of programming — is only as abstract as the depth with which you are willing to explore it.

Previous experience would help. But if you really think it through, all experience is previous experience. If I see something I’ve not come across yet, I just go ahead and spend half an hour studying it and move on… In a few day’s worth you’ll have firm knowledge of something new.

Anyway, I don’t want to insult the intelligence of the reader but I can’t make an accurate guess about how much you already know. You could be a beginner React programmer, you could be from Bangladesh or Arizona. Or you could be someone like Dan Abramov or Wes Bos. I don’t know!

Breaking Down the Process

To keep things simple in this tutorial I am only concerned with showing you how Sagas work. Breaking saga creation into easy to understand steps and getting you one step closer to better React architecture.

It takes about 7 steps to set up the Saga pattern. But that’s okay because once written it is nothing short of magic for reactive development.

It may seem like a big hassle at first but once you see it in action you’ll probably never want to go back to standard Flux or Redux patterns again.

Sagas have dramatically reduced development rate for the Goodyear app I helped making. And I can’t imagine not using it in any of my future React projects either. So let’s get started…

Here are the saga setup steps & <source code> that follows:

1. Install react, react-redux, react-router-redux and redux-saga packages.
2. Create your own API and add methods for each API event.
3. Create master saga generator list with yield fork briefly discussed above.
4. Write actual function methods for each saga event.
5. Create an accompanying reducer for each saga event method.
6. Create a master list for all reducer events.
7. Map state received from reducer to props using MapStateToProps method.

That last step is when the result of your saga action is received as a resource back from the API. This is the magic part. After dispatching an action the result becomes available on this.props property which is already automatically tied to UI redraw functions. Once sagas are fully and accurately set up for all your events… all you do is just dispatch actions.

The reducer returns a fraction of your state. That’s what it’s for. It literally “reduces” data only to the parts that have been changed. These changes then becomes accessible via this.props.someProperty on the component from which the event was dispatched.

If you’re with me so far then we’re ready to actually implement this in source code. However, one thing that really bugged me when I was just starting out is trying to figure out how the results returned from a saga reducer became available in props object so the view could be updated again.

But there is one more thing…

From the vantage point of programming with sagas once they are set up…

How do we receive the result of a saga on the other end?

Whenever the action returns… the data becomes magically available through this.props on the component itself. It could be a list of users or some other data that you can now pass to Array.map method for rendering your list or update the UI data with whatever else.

If the API call returns a JSON object you can convert it to an iterable Array using the Object’s keys or values method and loop through each object returned.

There is… one other thing… two types of events.

The event system of a saga architecture is two-fold. There are two different types of events you should be mindful of: saga events and reducer events. Thinking about it this way logistically helps us deal with the results.

Saga events are your primary events that you will dispatch manually from your React components. But when a saga action returns it will fetch return value from the API. This fetched return value is then passed to a reducer.

Reducer events should be created at this point. You will create a reducer event that simply mirrors a given saga event. Make sure they use different but similar names. A common strategy is to use a postfix or prefix:

For saga event UPDATE_USER_FETCH
Your reducer event could be UPDATE_USER_PUT

Just pair them somehow in a way that makes sense to you.

This second reducer event is usually used to finalize the UI view. For example, if your saga deleted a user from a table the reducer event will take care of updating the user object list and redraw the table without the deleted row.

Reducer events basically answer a question that goes something like:

Okay, an object has been deleted, added or modified, should we visually update the UI with the new data set?

In many cases this is required. The cases in which a reducer event is not required are usually a lot more rare but you will run into them. You can also use reducer events to do any additional clean up work regardless whether the UI is updated or not, according to your application design.

mapStateToProps

Finally, we need to map state to props so we can actually work with the data in the render loop of the React component. Remember that component state should never be changed inside the render loop itself. If you do that your React app will stall the browser and you will have to force-close the tab in which your app is running.

How do we fight this? mapStateToProps to the rescue. By mapping component’s state to props we prevent React from getting stuck in eternal loop. Doing it via a reducer ensures that entire state is not copied to props. Only the objects returned from saga’s reducer, which reduces side effects.

We can receive the object that was returned from saga’s reducer and use its properties to display something useful in the component’s render loop. Or even pass it down to its child components.

const { Users } = this.props;

render( Users.map((object, index)=>{}) );

Source Code

We’re finally ready to write some code. Let’s put all of what we went over into action. Let’s create a saga and reducer set for a table containing a list of data. The events will fetch, add, update and delete the entries.

I won’t show you how to create an entire application. Only how to create one component with a complete saga roundtrip. You can replicate this pattern to all your other views.

For this example I chose a component called Layout which will represent an arbitrary UI view. This Layout will have 4 sagas for basic CRUD operation.

Therefore… the modular structure of our application will look similar to this:

Modular structure:
      apis/layouts.js        The API - LayoutAPI object
sagas/layouts.js Saga for <layouts>
reducers/layouts.js Reducers for <layouts>

You can (and should) create all of the above files for each unique component not just <layouts>. Just rename layouts with component_name and LayoutAPI to ComponentNameAPI.

In addition we should also have an extra file that will list all of our saga generators for all existing components. This is the main saga list:

A list of all existing saga generators are stored separately in:
    sagas/index.js

And finally… the same for reducers. We’ll see how this is actually implemented later in this tutorial. For now you can keep the files blank.

Combined reducers:
    reducers/index.js

Okay — looks like we’re ready to start writing some code.

It all begins with the API definition. This step should be repeated for each individual component. Here we’re just dealing with the layout view.

apis/layouts.js

// first, include axios (or similar database talker)
import
{ axios } from “./axios”;
// endpoint root
const root = "/layouts";
export default class LayoutAPI {
  static get() {
return axios.get(root); }
  static edit(payload) {
return axios.put(`${root}/edit/payload.id`, payload); }
  static add(payload) {
return axios.post(root, payload); }
  static delete(payload) {
return axios.delete(`${root}/delete/${payload.id}`); }
}

Now let’s create all saga actions and name method functions:

sagas/main.js

import { takeLatest } from “redux-saga”;
import { fork } from “redux-saga/effects”;
import { layoutsFetchList,
layoutsAdd,
layoutsEdit,
layoutsDelete
} from “./layouts”;

We’ve just referenced method names that don’t yet exist. So let’s create them!

sagas/layouts.js

import { call, put } from “redux-saga/effects”;
import LayoutAPI from "apis/layouts"
export function* layoutsFetchList(action) {
const response = yield call(LayoutAPI.get);
const payload = response ? response.data : {}
// send returned object back to reducer as payload:
yield put({ type: 'LAYOUT_FETCH', payload);
}
export function* layoutsEdit(action) {
yield call(LayoutAPI.edit, action.layout);
yield put({ type: 'LAYOUT_SAVE', layout: action.layout });
action.callbackSuccess();
}
export function* layoutsAdd(action) {
yield call(LayoutAPI.add, action.layout);
yield put({ type: 'LAYOUT_ADD', layout: action.layout });
action.callbackSuccess();
}
export function* layoutsDelete(action) {
yield call(LayoutAPI.delete, action.layout);
yield put({ type: 'LAYOUT_DELETE', layout: action.layout });
action.callbackSuccess();
}

Note that the methods are very similar to each other with only slight differences. Each method contains a call to a reducer (LAYOUT_FETCH, LAYOUT_SAVE, LAYOUT_ADD and LAYOUT_DELETE) which occurs soon as the API call succeeds. This is the reducer call that sends back the returned object back to the component’s state and later to be converted to props via mapStateToProps function. Saga takes care of this internally, we simply need to implement it at the very bottom of our component and we will take a look at how that’s done in the last step of this tutorial that wraps everything up!

Right now, we just need to define layout reducers:

reducers/layout.js

// Collect results from methods
export default function
layouts(state = {}, action) {
  switch (action.type) {
    case 'LAYOUTS_UPDATE':
return action.layouts;
    case 'LAYOUTS_ADD':
const layout = action.layouts;
return [...state, layout];
    case 'LAYOUTS_EDIT':
return state.map(layout =>
Number(layout.id) === Number(action.layout.id) ?
{...action.layout} : layouts);
    case 'LAYOUT_DELETE':
return state.filter(layout =>
Number(layout.id) !== Number(action.layout.id));
    default: return state; // return initial state by default
}
}

When writing large applications you will need multiple reducers. In Redux you can combine your reducers into one object. This extra step is described below:

reducers/index.js

import { combineReducers } from “redux”;
import { routerReducer } from “react-router-redux”;
import { reducer as formReducer } from “redux-form”;
import { i18nReducer } from “react-redux-i18n”;
import users from "./users";
import tags from "./tags";
import templates from "./templates";
import layouts from "./layouts";
import auth from "./auth";
// main reducers
export const reducers = combineReducers({
routing: routerReducer,
form: formReducer,
users,
tags,
templates,
layouts,
auth
});

Here users, tags, templates and auth are included as fictional objects. The only object that actually exists in this tutorial is layouts. And you can create the other ones in the same way. This reducer combiner also adds routing and form reducers for future use. But the whole point of this step is to tie together all reducers into one object and export it to individual components.

Finally… our React component Layout is ready to be built. This will tie all of the code we’ve just seen together into complete implementation.

components/pages/layouts/index.js

import { React, Component } from “react”;
import { connect } from “react-redux”;
import { Link } from “react-router”;
import { push } from "react-router-redux";
export class Layout extends Component
{
constructor(props) {
super(props);
this.state = { };
this.addNewLayout = this.addLayout.bind(this);
}
  this.addLayout() {
// Create fictional payload:
let payload = {
name: "my layout",
pictures: 12,
medium: { charcoal: "yes", paper: "60LBS" }
};
// Dispatch the saga action to add a new layout!
dispatch({type: "LAYOUT_ADD"}, payload);
}
  render() {
let { layouts } = this.props;
return(
<div>
<h1>Layout Components</h1>
<ul>
layouts.map((object, index) => {
<li key = { index }>{ object.property }</li>});
</ul>
        { /* -- Click to execute the add new layout saga ! -- */ }
        <button onClick = { () => { this.addLayout() } }
value = "Add New Layout" />
      </div>);
}
}
{ /* -- Finally connect component to the magic of sagas -- */ }
function mapStateToProps(state) {
return { layouts: state.layouts }
}
export default connect(mapStateToProps)(Layout);

Note 1: It looks like your regular component. The most important thing here is the mapStateToProps and the connect method. This is the magic of sagas.

Note 2: Initiating a saga action is usually done from your component. In this case by clicking on a button. Just make sure to wrap your onClick attribute with {} and use an arrow function. Don’t call the method directly from {this.mySagaAction()} as this will trigger eternal re-rendering loop.

The correct way is: { () => { this.mySagaAction(); } }

Note 3: You will notice that this component does not import any of the sagas or reducers! That’s not a mistake. This is auto-magically taken care of in redux via global state object. Sagas nicely plug into that redux pattern behind the scenes. As I said in the beginning this tutorial assumes that you already understand and have redux with router set up. If not please head to a tutorial that explains this preliminary step.

Note 4: when layouts is read from props and iterated, you are required to use a key on iterable objects in React (for which purpose, you should use the key property on the iterable as in <li key = { index }>). Skipping this step will not break your app but React will soft complain to Chrome console.

I hope after reading this article you got a better understanding of sagas.

The End

I totally understand that this may contain mistakes, errors and things that I may have overlooked. If you spot anything please let me know so I can correct and make it an even better article!

Limited Time Offer

The diagrams in this tutorial were influenced directly by the manuscript!

CSS Visual Dictionary 28% OFF for Medium readers.

28% OFF

Medium Readers Only:

CSS Visual Dictionary

== grab a copy ==

Contains all CSS properties.