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
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.
Flux Standard Actions (FSA)
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:
Creating and handling actions
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.
Reusing action reducers
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:
- Selectors can compute derived data, allowing Redux to store the minimal possible state
- Selectors are efficient. A selector is not recomputed unless one of its arguments changes
- Selectors are composable. They can be used as input to other selectors
The following code snippet shows the memoized version of the previous selector function recreated using Reselect’s
getSelectedItems selector is a composition of two selectors namely
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:
Improved code reuse with selector props
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.
Re-reselect: Improved selector caching and memoization
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.
re-reselect package, derived selectors can be composed using the
createCachedSelector default export function instead of the
createSelector function from
createCachedSelector function returns a function that takes a
resolveFunction as its argument.
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
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.
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.
Set up the middleware
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
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.
Creating the saga
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
Finally, we created our sagas using the effects from
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
rootSaga, we watch for every
PHOTO_FETCH_REQUESTED action and trigger the photo fetch worker saga using 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
- call — Runs a function passing the specified arguments. If the function returns a Promise, it pauses the saga until the promise is either resolved or rejected
- put — Dispatches a Redux action
- fork — Runs the passed function in a non-blocking way
- take — Pauses the saga until the specified Redux action is received
- takeEvery — Returns result for every call triggered for the specified Redux action
- takeLatest — Returns the result of only the last call triggered for the specified Redux action, ignoring the rest. This effect can be used to implement some form of action cancellation
- race — Runs multiple effects simultaneously and terminates all of them once one is complete
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.
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:
Here is the signature of an epic by the way:
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
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.
Creating the epic
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:
- debounce requests within a short time frame
- terminate network requests for cancelled fetches
- retry the photo fetch request a maximum of 3 times on failure
- fetch a maximum of 10 photos and ignore subsequent requests
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.
Clap & follow
If you found this article insightful, feel free to give some rounds of applause if you don’t mind.
Plug: LogRocket, a DVR for web apps
LogRocket is a frontend logging tool that lets you replay problems as if they happened in your own browser. Instead of guessing why errors happen, or asking users for screenshots and log dumps, LogRocket lets you replay the session to quickly understand what went wrong. It works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store.