Redux Middleware : Soups to Nuts
TL;DR
After the instant gratification of a react application with redux-saga demo / walk through, you are left with the arduous task of figuring out, how the various players mesh together or more importantly why it is now broken after a single seemingly insignificant change.
Intent
This is a deep dive into redux-saga to help readers familiarize themselves with the nuts and bolts of the library, in the hope that they will be equipped to start fixing issues themselves.
Source Code: Complete solution of code illustrations available here
Acknowledgements
Shout out to Jasmine Bamou ! The article authored by Jasmine Bamou was the source of inspiration for this endeavor. Thank You!
Overview
In the bygone days, you could find a solution by diving into the source code of a totally home grown application.
Today, the name of the game is orchestration. You are leveraging code which is authored by an unknown entity. This makes sense from a "don't reinvent the wheel" philosophy. You no longer need to know every line of code. However you do need to familiarize yourself with the moving parts and understand how they mesh together. For instance, to drive a car you don't need to know how everything works but only how to use it.
The same is true for the libraries in this orchestration, knowledge of the library usage is key. This is the focus of this article.
The libraries in this orchestration are :
? react-redux ? redux-saga ? redux ? react
Prerequisites
Fundamental knowledge of React , JavaScript , ECMAScript 6 especially generator functions are required.
Introduction
Readers who are familiar with the libraries listed above can scroll ahead to the section "Code Sample Walk Through" and skip the introduction.
What is middleware?
Middlleware has different connotations in the software arena. In this context, it acts as a interceptor between the consumer (the application) and the original recipient (API endpoint). The middleware in this case defers the dispatch to the redux store until the API request is fulfilled.
The need for middleware.
Since redux is synchronous and consuming REST API endpoints with asynchronous requests is the industry trend. The middleware serves to bridge the gap between redux and the deferred API response. Both redux-thunk and redux-saga are middleware.
Redux-Thunk details here
What on earth is a thunk?
This is a ingenious approach for deferred execution.Below is one description.
A thunk is a function that acts as a wrapper in that it wraps an expression to delay its evaluation
In simple terms a thunk is a function that returns a function which in turn returns another function.
The outer function passes a reference to the redux dispatch and getState parameters while the inner function is a action creator. Action creators is a fancy term for a function that returns an action object.
How does redux-thunk work ?
redux-thunk examines each incoming request:
- when it is a plain action object, it's a pass through to the store's reducer.
- when a function a.k.a action creator is detected, redux-thunk returns the action creator to the middleware. The middleware then defers the execution until the asynchronous response is received. The action is then extracted and then dispatched to the store.
The related code segment is below:
Redux-Saga details here
Redux-Saga is a library that aims to make application side effects (e.g., asynchronous actions such as fetching data) easier to handle and more efficient to execute.
Overview
Sagas are implemented as Generator functions that yield objects to the redux-saga middleware.
The yielded objects are a kind of instruction to be interpreted by the middleware. When a external side effect is yielded to the middleware, the middleware will suspend the Saga until the side effect completes.
What is a Saga Effect?
Effects are plain JavaScript objects which contain instructions to be fulfilled by the middleware.
A saga effect is an abstraction which leverages the ES 2016 generators to declare effects as well as permitting their composition . Generators allow a simple composition sequence with a list of yield statements one after another. The saga library also provides control flow operators (like if, while, for) which can be used in combination to implement more sophisticated workflows.
The saga library adopts the generator functions to defer execution in order to consume asynchronous REST API requests. When a middleware retrieves an Effect yielded by a Saga, the Saga is paused until the Effect is fulfilled.
Saga Effects (put, call) along with its API (takeEvery) permits you to implement redux-thunk like functionality. This use case scenario is demonstrated in the code sample and the illustrations below. The solution illustrated here has the additional benefit of unit testing without any mocking required, although no tests are implemented in the code sample.
Where redux-saga library shines is the repertoire of effects which can be effectively used to compose sophisticated workflows . Feel free to explore more here
Also it is significant to note here that the middleware is provided a saga array . The saga infrastructure will execute each saga element in the array in parallel out of the box.
Code Sample Walk Through
Orchestration steps
Step 1 - Code Segment : React Component (App.js)
There are four touch points that hook up the component to the redux store by leveraging react-redux library . See below illustration focusing on the red bullet points and the comments on the right with the same bullets.
- Reference to the connect Higher Order Component HOC (line 2)
- Define the function hook to dispatch the action when invoked (line 9).
- Wire the click event to the action creator on the component.(line 23)
- Pass the related function to the connect HOC. (Line 30)
Step 2 - Code Segment: Redux Reducers
Below is an sample redux reducer in its simplest form. A reducer is basically a JavaScript switch statement which returns the corresponding state for each case (action).
In a complex real-life situation, there would probably be more reducers. The recommendation is to organize the reducers in the application in functional blocks or in separate files as described by the redux documentation here. The redux library also provides a combineReducers utility which aggregates all the reducers into one unit. This is required as the redux API accepts only one reducer (rootReducer) as you will see in another section.
- Reference to the combineReducer utility (Line 2).
- Return the existing state with the state updates required. In this case, this would be data payload from the API call (Line 8).
- Return the existing state along with the updated fetching flag value. This is to indicate that the API call is in flight.(Line 16).
- Leverage combineReducers redux utility to combine into a single reducer. There's one reducer here but in a real life scenario there would be multiple depending on the complexity of the application.
Step 3 - Code Segment: Saga Effects
Saga library provides the developer sophisticated repertoire of features. The saga library also provides some saga helpers for some common use case scenarios to ease the integration effort.
- The takeLatest api function listens for the dispatched action and then invokes getData function (Line 26).
- The put statement is an example of a effect. In this case execution is suspended on line 12 until the async function on line 5 returns.
- This Saga yields an saga array. Each element in the array will be invoked in parallel (Line 29).
Step 4 - Code Segment: Redux-Saga Integration
- Pay attention to references in the redux and saga libraries. Bullets 1,2 on line 2 and 5 respectfully.
- createSagaMiddleware creates a redux middleware on line 9 which is consumed by redux on line 17 using helper createStore.
- Dynamically run the saga (line 25). This must be after applyMiddleware on line 17. see
Conclusion
I am glad that you have been able reach this section. I know, there's a lot to absorb here! My intent was to dwell sufficiently into the redux-saga topic so that the information provided could be adopted into a full blown production implementation. There are also a generous sprinkling of hyperlinks to provide more context and details to research further.
References
#reactjs #middleware #redux #redux-saga #redux-thunk