Redux is a very popular state container used in so many modern frontend JavaScript applications. It is framework agnostic and can be used in apps built either with vanilla JavaScript or any of the modern JavaScript frameworks like React, Angular, VueJS, etc.
One of the greatest benefits of using Redux is that the application’s state is global and in one place usually called a store. The Redux architecture leverages on the concept of actions and reducers for triggering and handling state changes in the store. This makes state management and change detection across the app very predictable.
When working on a project, Redux is usually used alongside other libraries for enhancing the state management process across the application.
In this article, we will explore 5 popular Redux libraries for improving code reuse across apps. Here is a quick list of the libraries in this article:
The majority of the code snippets in this article will be based on React components connected to a Redux store container via react-redux
.
Flux Standard Actions in Redux.
Redux actions provide a declarative mechanism for describing intents that can alter the application state, leaving the implementation of such intents to other aspects of the app (reducers). This design pattern is very similar to the state management pattern used in Flux.
However, with Redux, you get a lot of verbose boilerplate code. You are always trying to keep track of the names of action type constants in your reducers and action creators. This may sometimes be overwhelming and that is where Redux-Actions come into play.
Working with actions in both Flux and Redux can be a lot easier if they conform to a consistent structure. That is why the Flux Standard Action (FSA) specification was created, to standardize actions to conform to a consistent and human-friendly structure.
Redux-Actions is a very lightweight package for creating and handling Flux Standard Actions in a Redux application. The following code snippet shows the format of a typical FSA:
Let’s say we want to create a very simple pausable counter widget for an application. Usually one of the most basic actions for the counter will be an action to increment the value of the counter. We can create this action and a reducer for it using redux-actions
as follows:
Simply incrementing the counter isn’t enough fun for our widget. Let’s say we added a flag to the state that indicates whether the counter is incrementing. We can define an additional action and reducer to handle toggling this flag. However, we can use handleActions
to create a single reducer that handles the two actions.
Here is a complete code snippet showing what the store will look like:
You can get a live demo of the counter widget on Code Sandbox.
One major benefit of using redux-actions
to create actions is that it makes it possible to isolate action reducers, which in turn enhances the reuse of action reducers in other parts of the application state with similar requirements.
A very good example of a reusable reducer is one that handles loading state for asynchronous operations. Here is what it could look like:
Here we have created a wrapper for augmenting an already existing state object with loading state. This wrapper can then be used to create several state objects with loading state and their corresponding reducers. Here is a simple demonstration:
You can get a live demo on Code Sandbox showing how to reuse loading state logic in different parts of an application.
Memoized selectors for Redux.
When using Redux, one thing you’ll be doing frequently is accessing the global state in different parts of your application. A Redux store provides the getState()
method for getting the current state of the store.
However, the thing with this method is that it returns the whole state of the store, even though you may only be interested in small chunks of the overall state.
Redux uses functions of state known as selectors for selecting chunks of the overall state. A typical selector function will look like the following:
The problem with the getSelectedItems
selector function is that it is not memoized. As a result, every change in the Redux store’s state will require the selector function to be recomputed. This is where the Reselect library comes in.
Reselect is a simple library for creating memoized, composable selector functions. Reselect selectors can be used to efficiently compute derived data from the Redux store. Here are the main advantages of using selector functions created with Reselect:
The following code snippet shows the memoized version of the previous selector function recreated using Reselect’s createSelector()
function:
Here, the getSelectedItems
selector is a composition of two selectors namely getItems
and getSelected
, using Reselect’s createSelector()
function. Compositions like this make it possible to build specialized selectors that compute different forms of derived data from the state.
For example, a new selector can be created from the getSelectedItems
selector, that returns the total amount payable for the selected items less the discounts. Here is what it will look like:
This demonstrates how easily selectors can be composed of other selectors and consequently improve code reuse.
These selectors can then be used to connect a React component to the Redux state using react-redux
as follows:
To further improve code reuse, Reselect’s selector functions can take a second props
argument which maps to the props of the connected component. So, several component instances can dynamically derive data from the store’s state using the same selector function but with different props.
Let’s say we want to recalculate the item prices in another currency based on the component’s props. All we have to do is modify the prices of the items on the getItems
selector based on the currency specified in the props
received as the second argument.
The following example demonstrates what this looks like:
The interesting thing about this is that all other selectors that are derived from the getItems
selector will also get their derived data updated as necessary.
Building selector functions based on props leads to some trade-offs on the optimization of the resulting selector.
This is because reselect
keeps a cache with a limit of 1
entry for every selector that has been called. So, when a selector is called with different props, the cache gets invalidated.
One way to deal with this is by leveraging on the re-reselect package for creating selectors with deeper memoization and expanded cache.
This package ensures that a cached selector is used instead of a fresh one whenever a selector function is called with arguments it has never been called with before. It is able to do this because it uses some form of cache key to determine if a selector has been cached before.
With the re-reselect
package, derived selectors can be composed using the createCachedSelector
default export function instead of the createSelector
function from reselect
.
However, the createCachedSelector
function returns a function that takes a resolveFunction
as its argument.
This resolveFunction
is defined with the same parameters as the resulting selector and must return a string representing the cache key to be used for caching the resulting selector.
Here is what our previous selectors will look like using the createCachedSelector
function:
Better side effects management and testability for Redux.
Redux, as a state manager, does a good job in handling synchronous actions across an application. However, most applications require involving a lot of asynchronous actions at different levels of complexity such as DOM events, AJAX requests, etc. These asynchronous actions can be referred to as side effects.
This is where Redux-Saga comes in handy. Redux-Saga makes it possible to handle application side effects easily, efficiently and in a predictable way. Redux-Saga is a Redux middleware, hence it has full access to the Redux application state and can dispatch Redux actions as well.
Redux-Saga uses sagas for handling side effects. A saga is like a separate thread in the application with the sole responsibility of handling side effects. Redux-Saga depends on ES6 generators for controlling asynchronous flow. So, by the implementation, sagas are expected to be generator functions.
If you are already used to using the redux-thunk middleware package for handling asynchronous actions, then you will immediately notice the benefits of using Redux-Saga.
While redux-thunk
depends on action creators and lazy dispatching, redux-saga
depends on effects and sagas which makes code maintainable, easily testable and easy to achieve execution flows like delayed execution, parallel execution, race execution, etc.
First off, you have to set up and apply the redux-saga
middleware on the Redux application store. The following code snippet shows the setup:
Here, we have set up a simple Redux store with some actions for a dummy photo application. We also enhanced the store with a saga middleware created using the redux-saga
package.
Finally, we run a saga exported as rootSaga
through the saga middleware. At the moment, we don’t have this saga defined, so we will go ahead and create it.
As stated earlier, sagas are generator functions. Generator functions are one of the major additions in ES6 and they are very useful when it comes to handling asynchronous execution flows because of their ability to halt and resume code execution.
You may be interested in knowing a bit about generator functions before you continue. The following code snippet shows a simple generator function:
Now here is what the sagas.js
file containing the root saga looks like:
In this code snippet, we began by importing some special functions called effects from the redux-saga
package. Next, we create two helper functions: one to test for plain JavaScript objects and the other to fetch photos from the Picsum API.
Finally, we created our sagas using the effects from redux-saga
. The photoFetchWorkerSaga
, when triggered, fetches a photo from the Picsum API based on the action payload.
If the fetch was successful, it dispatches the PHOTO_FETCH_SUCCESSFUL
action. Otherwise, it dispatches the PHOTO_FETCH_FAILED
action.
In the rootSaga
, we watch for every PHOTO_FETCH_REQUESTED
action and trigger the photo fetch worker saga using the takeLatest
effect.
However, the takeLatest
effect only returns the result of the last call and ignores the rest. If you are interested in the result of every call, then you should use the takeEvery
effect instead.
Here is a brief list of some of the effects provided by the redux-saga
package:
Powerful side effects handling for Redux using RxJS.
Although Redux-Saga does a pretty good job at managing side effects and easing testability, it is worth considering the Redux-Observable package. Redux-Observable allows you to get all of the reactive programming awesomeness that comes with using RxJS while handling side effects in your Redux application.
To use redux-observable
, you will also need to install rxjs
as a dependency for your application, which means you need to have an understanding of RxJS Observables. Redux-Observable uses epics for handling side effects in a very powerful way.
An epic is simply a function that takes a stream of actions and returns another stream of actions. Redux-Observable automatically subscribes to each registered epic under the hood, passing the Redux store dispatch
method as an observer, something like this:
epic(action$, state$).subscribe(store.dispatch)
Here is the signature of an epic by the way:
function (
action$: Observable<Action>,
state$: StateObservable<State>
): Observable<Action>;
Inside an epic, you can use any of the Observable patterns provided by RxJS as long as you always ensure that the final stream returned by the epic is an action. Here is a very simple epic:
This epic listens for every 'PING'
action and maps them to a new 'PONG'
action. It causes a 'PONG'
action to also be dispatched whenever the Redux store dispatches a 'PING'
action.
Just like with Redux-Saga, a middleware setup is required to enable Redux-Observable to listen for actions on the Redux store. Here is a code snippet showing the middleware setup:
Here, we have created a middleware setup and store configuration very similar to the one we created before for the Redux-Saga example.
Notice, however, that Redux-Observable epic middleware expects to run only one root epic. Therefore, all epics for the application need to be combined into one root epic just like with Redux reducers.
Like we saw earlier, an epic is a regular function that can take an action$
stream and optional state$
stream as arguments and returns another action stream.
Inside of the epic function, any valid Observable pattern provided by RxJS can be used, which is where the real power comes.
The following code snippet shows an epic for the photo fetching application we had earlier:
Although it seems we have written more code using Redux-Observable than we wrote using Redux-Saga, the real advantages come with chaining RxJS operators.
For example, let’s say we want to modify the photo fetching operation like so:
All we have to do is to simply chain some RxJS operators to our already existing epic and we are good. Here is what this will look like:
Normalize nested JSON according to a schema.
A large chunk of the data living in the Redux store of an application usually comes from making an AJAX request to some API at some point in the application.
Most of these APIs return JSON data that has deeply nested objects and using the data in this kind of structure is often very difficult for our Redux applications. That is where Normalizr comes into play.
Normalizr is a very lightweight and powerful library for taking JSON with a schema definition and returning nested entities with their IDs, gathered in dictionaries.
A simple example will quickly show how useful Normalizr can be for normalizing nested JSON data according to the schema. But first, let’s try to create a normalization schema.
Let’s say we have JSON data for a news feed that looks like the following:
We can define the schema for this JSON data using Normalizr as follows:
After creating the schema, we can use it to normalize the JSON data as follows:
Here is what the normalized data will look like:
In this tutorial, we have been able to explore 5 libraries commonly used with Redux for improving code reusability and also building powerful applications.
If you found this article insightful, feel free to give some rounds of applause if you don’t mind.
You can also follow me on Medium (Glad Chinda) for more insightful articles you may find helpful. You can also follow me on Twitter (@gladchinda).
Enjoy coding…
Install LogRocket via npm or script tag. LogRocket.init()
must be called client-side, not
server-side
$ npm i --save logrocket // Code: import LogRocket from 'logrocket'; LogRocket.init('app/id');
// Add to your HTML: <script src="https://cdn.lr-ingest.com/LogRocket.min.js"></script> <script>window.LogRocket && window.LogRocket.init('app/id');</script>
Would you be interested in joining LogRocket's developer community?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowMaking carousels can be time-consuming, but it doesn’t have to be. Learn how to use React Snap Carousel to simplify the process.
Consider using a React form library to mitigate the challenges of building and managing forms and surveys.
In this article, you’ll learn how to set up Hoppscotch and which APIs to test it with. Then we’ll discuss alternatives: OpenAPI DevTools and Postman.
Learn to migrate from react-native-camera to VisionCamera, manage permissions, optimize performance, and implement advanced features.