Redux Selectors, Sagas, Middlewares and more…

Ale Arce
12 min readMar 3, 2018

--

Intro

In my first post I wanted to share some appreciations I have regarding specific practices in the frontend development when implementing Redux. I will try to explain how you can shape your assets in the frontend so you can keep an organized structure. I will try to improve code examples gradually, from simple to more complex but robust code…

If I am lucky, I will receive some feedback on this and then I can perform updates on this post.

In several parts of the article, I point to the Reducers Recommended Structure section, you may want to read it first and then re-read it when necessary, due this reducers structure is one of the core concepts of the article.

I assume that you have basic knowledge of Redux and strong knowledge of ES6. Also I will be using React to build example components.

What we will cover

Folder Structure
Why Smart and Dumb Components
Standard Actions
Middlewares
Redux Selectors
Reducers Recommended Structure
Store Config

Folder Structure

I found this structure very effective. By no means I intend to say it will be useful for you, but in order to understand some of the examples I provide on this post, it’s necessary to see the files and folders structure, plus a brief summary of each one of them. I took this example form a previous project I was working on, I will focus on the redux-related parts:

Recommended Folder Structure

/actions

Contains business-entity-grouped actions. We will go deeper on this.

/api

This folder contains the API communication layer of the project.

/components

Dumb components, no business logic, no redux needed.

/constants

I recommend to place one single file containing all the string constants representing action names. I will provide more detail for this file, and of course you may decide to have several files instead of a single one.

/containers

Smart components, specific business logic, redux needed.

/reducers

Pretty self explanatory, reducers go here.

/sagas

Here we will place the needed sagas. I will provide an explanation of redux-sagas, however, it’s a quite advanced concept and I’m pretty much a beginner on this.

/stores

Here we create the store, one of the key parts of Redux. We apply the middlewares to the store as well.

Why Smart and Dumb Components?

One of the most important things when using redux, is to keep in mind that you should follow a “reusable components” philosophy. More precisely, inside the Redux world most of the developers have taken an smart-components and dumb-components approach:

Smart Components

Those that have specific business logic and probably are specific to your app. These components use Redux, because they attach (connect) themselves to the parts of the app-state they need in order to work. This is what Redux is meant for. An example of a smart component would be a UsersListComponent, or may be a BirthdaySelectComponent.

Dumb Components

They are context / business agnostic, and this makes them fully reusable. So, an example of a dumb-component would be a ListComponent. This is not a UsersListComponent, or a ProductsListComponent. Dumb components don’t have business knowledge, they are fully reusable as long as you provide “the firm” they need to render, and most importantly, they don’t need Redux, because all its data is provided by some other smarter component.

Redux is a framework to maintain the app’s state, why am I talking about smart and dumb components?

Smart components will use redux, dumb components won’t. This is important for this article given that I’m trying to explain a way to keep your code clean. If all your components access the app state, your code can get messy very quickly.

Standard Actions

I would describe actions as public notices, distributed within your app. This means that someone will trigger an action and some other subscribers may do something regarding that action. Pretty much like an observer pattern, but cooler. Actions must first be defined, and then they can be triggered, intercepted and analyzed to do something about them. These are examples of action definitions:

https://gist.github.com/alelr36/6b4f8a3aaee069e4bd6000c7934ab511

A bit more detail regarding createAction

createAction is a function that receives 3 parameters: actionName, payloadCreator and metadataCreator. I won’t deepen that much on this topic, but here’s a basic explanation:
- actionName: a string representing the identifier of the action.
- payloadCreator: a function definition, that will receive the arguments provided by the action invoker, and returns the payload accessible in reducers watching the action (action.payload).
- metadataCreator: a function definition, that will receive the arguments provided by the action invoker, and returns the metadata accessible in reducers watching the action (action.metadata).
Deciding if something is payload or metadata, is up to you.

Note here that actions are function definitions that expect to be invoked with some data. In this example, selectProduct is a function that expects to be invoked with an object, locally called product. The action definition trusts that the product will have an id property. As this action definition is returning product.id, that id will be the payload in the reducer subscribed to the action PRODUCTS_SELECT. If we want we can send the entire product to the reducer, by simply doing product => product).
Im my experience, it’s very common to see lodash usages (find, get, filter, reduce, first), in these action definitions. I believe there’s no problem with that. Remember, what payloadCreator returns, will be action.payload in the reducers watching for the action, and that’s what you will be able to include in the app’s state. We want to keep the state as dumb as possible, very simple. If we need to transform / decorate the simple data we stored in the app’s state, we can do it by using selectors.

Given those two defined actions, some smart component (consisting of two files), may do something like this:

https://gist.github.com/alelr36/a6c9e136e2b1dced76d69f248d62e147

At this point, this component data definition (index file), enables access to four elements (props) in the proper products.jsx component:

  • this.props.products: connected to state.products.productsList.
  • this.props.selectedProduct: connected to state.products.selectedProduct.
  • this.props.selectProduct: a function I can call, which will result in an action (PRODUCTS_SELECT)
  • this.props.fetchProducts: a function I can call, which will result in an action (PRODUCTS_FETCH)

Note that this smart component is triggering actions differently:

  • this.props.selectProduct(product) is triggered and it includes data for the payloadCreator.
  • this.props.fetchProducts() is triggered but it doesn’t include a payload, because it’s not needed so far.

If you want to send a payload when executing an action, you need to provide arguments to the function call, i.e. this.props.myActionName({some: 'payload'}, {some: 'metadata'}). These parameters are received by the action creators, like product is being received by selectProduct, which takes it and returns product.id. All reducers expecting for a PRODUCTS_SELECT action, will receive the new id.

Until now, we have have defined two actions, a component connected to those actions and to some parts of the state as well. The next step is understand the needed reducer, which will receive the payload and update the corresponding part of the state. By doing this, all the components connected to those parts of the state, will receive the new version of the state.

You may find this reducer example a bit strange, I haven’t completely described my approach to reducers yet. You can take a look at that section now if you want.

Now, the Redux cycle on this practical example:

  • A smart component is mounted and it triggers this.props.fetchProducts(). No payload needed.
  • The action definition for fetchProducts is per now returning a hardcoded list of products, that will end up in the reducer watching for the action PRODUCTS_FETCH.
  • The reducer updates the state with the list of products. All components connected to state.products.productsList will receive the update.

Particularly, the containerproducts.jsx will now iterate through the provided array instead of an empty one (initial state).

Middlewares

Middlewares are fragments of code you integrate to your app. They work in the middle of the actions pipeline, analyzing every action and deciding if they should do something about it or not, and then passing the action to the next middleware or actor in the pipeline. For this article I want to introduce the following middlewares:

Redux Thunk
Redux Promise Middleware
Redux Saga
Redux Logger

I have found these middlewares really useful. These tools provide a mechanism to improve the actions workflow in the web app and keep it clean and logic. You can find how to include middlewares in the store config section. Let’s talk about each one of them.

Redux Thunk

So far, I’ve been talking about reducers returning new versions of the state, i.e. returning an object. The redux-thunk middleware checks if the payloadCreatorreturns a function instead of a plain object. If the payloadCreator is returning a function, then the middleware invokes that function with two parameters: dispatchand getState, both functions.

I wrote this based on the official examples, the code can be improved but I wanted to leave it as explicit as possible.

My goal with this example is to show the following:

  1. An action to select a product is created.
  2. A thunk to select a product is created as well.
  3. The thunk checks if the payload provided is the same as the existing in the state.

3.A. If the data is the same, it returns null (single return statement).

3.B. If the data is different, then it dispatches the proper action.

Instead of directly returning a payload and which will update all the related reducers, you check something to decide between executing the action or not.

Why would you select a product that is already selected? Redux Thunk enables you to evaluate some criteria before dispatching an action. You would not select a product which is already selected, right?

Redux Promise Middleware

I find this middleware a bit complicated to explain, so I’ll do my best.

Remember that middlewares are (in part) action analyzers. In this case, to use redux-promise-middleware, the function payloadCreator needs to return an object whose only key is promise, and it’s value is a promise instance, i.e. {promise: promiseInstance}.

Redux-promise-middleware detects this and automatically modifies the default action pipeline, avoiding the dispatch of PRODUCTS_FETCH, and producing two possible results:

  • PRODUCTS_FETCH_PENDING
  • PRODUCTS_FETCH_FULFILLED

Or…

  • PRODUCTS_FETCH_PENDING
  • PRODUCTS_FETCH_REJECTED

This two flows represent the possible states of a promise. With this, your reducers watch for actions _PENDING, _FULFILLED and _REJECTED.

Why is this useful? Let’s see:

  • By watching _PENDING you can set some loading value, in order to activate spinners or loading components in your front-end.
  • By watching _FULFILLED you will receive in action.payload, the data provided by the back end response.
  • By watching _REJECTED you can specify error messages based on the back end response.

Handling promises made easier.

With this, your components connected to these parts of the state, can logically change their content when these actions produce a change in the state. The action PRODUCTS_FETCH will never be dispatched. Instead, the middleware will ensure that PRODUCTS_FETCH_PENDING and the corresponding _FULFILLED or _REJECTED are thrown.

Let’s see a possible component’s code.

This last example requires at least an intermediate knowledge of ES6.

Redux Saga

I believe Sagas is a quite advanced concept, so I will be giving some very basic approach to understand what they do, and how to implement them.

Until now, we went through the main actors of Redux. If I was clear enough, you we can agree that so far, the only listeners / watchers of actions, are the reducers. The goal of a reducer is modify a specific part of the app’s state. I would describe a Redux Saga as actions watchers as well, but in this case they do not modify the app’s state. What a saga does, is to execute some code after an action is dispatched. This is useful in some specific cases.

Given an action:

  • You want to dispatch another specific action, or several actions.
  • You want to change the url.
  • You want to store something in sessionStorage.
  • etc…

I will take the first case as an example to show you how to use Redux Sagas. Let’s continue with the last example of products/index.js and products/products.jsx.

So far, when state.products.productsList is an array of products, products.jsx will render an ul > li of products. With the current code, clicking on any of those li will trigger a PRODUCT_SELECT action. Let’s put a class on the selected li:

You can use classNames to correctly implement a logical className approach. I just want to keep this as a stand alone example.

Now, you can style that .selected class to see the result. But we have a problem, what happens on the first load, before any click?

state.products.selectedProduct is an empty string, so at the beginning, no product will be selected. Let’s suppose we want to select the first product by default, when the list loads. We have at least two ways to do it:

Modify the reducer, so with the _FULFILLED action not only the productsList will be returned, but also selectedProduct:

Or, in the other hand, we can take advantage of this case to implement Redux Sagas and properly dispatch a PRODUCT_SELECT action after a PRODUCTS_FETCH_FULFILLED action is completed:

Redux sagas use generator functions, which is an entire concept by itself, so I won’t go deeper on this.

Redux Logger

This middleware is not precisely the most functional one, but in the development phase, I think it’s a great resource. Basically what offers you is a console output, directly on the browser. There are several other tools that do the same and much more, redux-devtools for example, but I wanted to mention Redux Logger given that it’s quite comfortable for me in development.

The result you will accomplish is something like this:

You can configure the action log to be collapsed / expanded by default. You can check the entire list of actions being dispatched, the app’s state before and after, and also the payload of each action. I find this quite cool.

To implement this middleware, you just have to include it in your store.

Redux Selectors

I wanted to introduce the concept of selectors because they are really helpful to keep our code decoupled and our components clean.

Let’s suppose you receive a list of products like this one:

This array would be in your state.products.productsList after a _FULFILLEDaction is dispatched. To display the dates, probably you will want to format their values using moment, and you would be tempted to do so inside the component:

This is, creating a new array of all the products but with each date formatted, inside mapStateToProps. The method mapStateToProps should be, as it’s name stands, a simple map between the state and the component’s props. But here the component should not be decorating or calculating anything.

If you keep transforming / decorating inside your components, your code can quickly get messy. Keeping in mind that the app’s state must remain as simple as we can, all the transformation, calculation and decoration of data will be done in selectors.

Selectors can be just functions that receive the state, perform all the needed work, and return the result to the components. That would be the same thing I just described but with the function definition somewhere else.

We will use a better approach, implementing createSelector, a method provided by the library reselect. By doing this, our selectors will be memoized, which basically means that our selector functions will be executed only when a part of the state they are watching changes (i.e. they will be executed only when needed).

And then, your smart component would pass the state to the selector methods, connecting to a decorated / calculated part of the state.

With this, we have implemented a selector to transform our dates, but also we included getSelectedProduct, which is a selector that calculates the selected product object (with all it’s data, not only the id as we had before). The state remains simple and it doesn’t repeat data. The selector functions, based on that simple data, decorate and calculate new data, and then provides it to the components.

To conclude the example, our component render method would also change:

Reducers Recommended Structure

In first place, I’ve seen a lot code like this:

First issue, your action types are completely hardcoded. Second, you are using a switch. Third, which is your initial state? It’s the {productsList: [], selectedProduct: ''} fragment, really long line if your initial state grows, and not clear to see if some new dev enters to the team…

In addition, let’s suppose your back end returns your products like this:

You may find this rare or impossible. Trust me, API responses can be even worst than this. In this case, your state would receive a deeply nested object as data… Not cool at all.

I think that these reasons are enough to provide some advice in the Reducers structure.

In first place, define an action-names-constants-file at a project level, your actions names will be defined there. Here’s a simple constants file defining actions:

But I would advice to do something like this instead:

key-mirror-nested is a library I find very useful for this case. Here I export an object with one key, PRODUCTS, and if you try PRODUCTS.FETCH, that object will have the value PRODUCTS_FETCH. So that’s the constant I need. Notice that I used underscore as connChar, you can use whatever you prefer.

And then your reducer would be something like:

By doing this

  • The initial state is very clear
  • Action types are not hardcoded
  • We are using an object notation instead of a switch
  • And we are clearly resolving the deeply nested problem the API may be sending us.

Finally, I recommend to have src/reducers/index.js to centralize all your reducers and keep a clear idea of your state:

This file will then be consumed when we configure our store.

Store Config

This is just an example of how to declare your store. You may find better / cleaner ways to do it. I believe this part of code was needed in the article, given that I mention so many things regarding middlewares. The configureStore is where you reference your reducers/index.js file, creating the store with the data you defined in your reducers, and attaching all the middlewares you want to the actions pipeline.

Then you can have an index.jsx file, which will simply import the configureSture function exported by configureStore.js:

--

--