



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.

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:

// Basic Flux Standard Action // with optional `meta` object { type: 'PHOTO_FETCH_REQUESTED', payload: { photoId: 875 }, meta: { timestamp: Date.now() } } // Error Flux Standard Action { type: 'PHOTO_FETCH_FAILED', payload: new Error('Photo not found.'), error: true }

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:

import { createAction, handleAction } from 'redux-actions'; // Default state const INITIAL_STATE = { counter: 0 }; // Create the action const increment = createAction('INCREMENT_COUNTER'); // Create the reducer const incrementReducer = handleAction( increment, (state, action) => { return { ...state, counter: state.counter + 1 }; }, INITIAL_STATE );

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:

import { createStore } from 'redux'; import { createAction, handleActions } from 'redux-actions'; const INITIAL_STATE = { counter: 0, counting: false }; const increment = createAction('INCREMENT_COUNTER'); const toggle = createAction('TOGGLE_COUNTING'); const reducer = handleActions( { [increment]: state => ({ ...state, counter: state.counter + 1 }), [toggle]: state => ({ ...state, counting: !state.counting }) }, INITIAL_STATE ); const store = createStore(reducer, INITIAL_STATE); export default store;

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:

import { combineReducers } from 'redux'; import { createAction, handleActions } from 'redux-actions'; // Basic loading state const LOADING_STATE = { counter: 0, active: false }; const KEY_REGEX = /^[a-z]+(_[a-z]+)*$/i; export default function withLoadingState (key, initialState, initialReducer) { if (!(typeof key === 'string' && KEY_REGEX.test(key))) { throw new Error(`Cannot use invalid key: '${key}'.`); } const KEY = key.toUpperCase(); // Create loading state actions for the given key // Actions include: reset, start and complete loading state const reset = createAction(`${KEY}_LOADING_RESET`); const start = createAction(`${KEY}_LOADING_START`); const complete = createAction(`${KEY}_LOADING_COMPLETE`); // Create reducer for the loading state actions const loadingReducer = handleActions( { [reset]: state => ({ ...LOADING_STATE }), [start]: state => ({ ...state, active: true }), [complete]: state => ({ ...state, active: false, counter: state.counter + 1 }) }, LOADING_STATE ); // Combine loading state with current state const state = { current: initialState, loading: LOADING_STATE }; // Create a combined reducer for both loading state and current state const reducer = combineReducers({ current: initialReducer, loading: loadingReducer }); // Export the final state, reducer and actions return { state, reducer, actions: { reset, start, complete } }; };

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:

import { createActions, handleActions } from 'redux-actions'; import withLoadingState from './with-loading-state'; const POST_STATE = { data: null, error: null }; const { fetchSuccessful, fetchFailed } = createActions('POST_FETCH_SUCCESSFUL', 'POST_FETCH_FAILED'); const postReducer = handleActions( { [fetchSuccessful]: (state, action) => ({ ...state, error: null, data: action.payload.post }), [fetchFailed]: (state, action) => ({ ...state, error: action.error.message ? action.payload : null }) }, POST_STATE ); /** * Augmenting current post state with loading state * Returned object contains: state, reducer and actions * * postWithLoading = { * state: { * current: { data: null, error: null }, * loading: { active: false, counter: 0 } * }, * reducer: (Fn...), * actions: { * reset: { type: 'POST_LOADING_RESET' }, * start: { type: 'POST_LOADING_START' }, * complete: { type: 'POST_LOADING_COMPLETE' } * } * } */ const postWithLoading = withLoadingState('POST', POST_STATE, postReducer);

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:

function getSelectedItems(state) { const items = state.items; const selected = state.selected; return items.filter(item => selected.includes(item.id)); }

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

, allowing Redux to store the minimal possible state Selectors are efficient . A selector is not recomputed unless one of its arguments changes

. A selector is not recomputed unless one of its arguments changes Selectors are composable. They can be used as input to other selectors

Composing selectors

The following code snippet shows the memoized version of the previous selector function recreated using Reselect’s createSelector() function:

import { createSelector } from 'reselect'; const getItems = (state) => state.items; const getSelected = (state) => state.selected; const getSelectedItems = createSelector( [getItems, getSelected], (items, selected) => items.filter(item => selected.includes(item.id)) );

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:

const getSelectedItemsAmount = createSelector( [getSelectedItems], (items) => items.reduce((total, item) => { return total + Number(item.price) - ( Number(item.discount) || 0 ) }, 0).toFixed(2) );

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:

import React from 'react'; import { connect } from 'react-redux'; import { getSelectedItems, getSelectedItemsAmount } from './selectors'; function SelectedItemsList(props) { return ( <React.Fragment> <ul> { props.selectedItems.map(item => ( <li key={item.id}> {item.name} { item.price } (Over { Math.floor(item.discount / item.price * 100) }% Savings) </li> )) } </ul> Overall Amount: { props.selectedItemsAmount } </React.Fragment> ) } const mapStateToProps = (state) => ({ selectedItems: getSelectedItems(state), selectedItemsAmount: getSelectedItemsAmount(state) }); export default connect(mapStateToProps)(SelectedItemsList);

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:

// BEFORE (Without Price Conversion) const getItems = (state) => state.items; // AFTER (With Price Conversion) const getItems = (state, props = {}) => { const currency = `${props.currency}`.toUpperCase(); const RATES = { GBP: 0.78, EUR: 0.88, YEN: 6.94 }; // Usually the rate will be gotten from an external service const rate = Object.keys(RATES).includes(currency) ? RATES[currency] : 1.00; return state.items .map(({ price, discount, ...item }) => ({ ...item, price: (price * rate).toFixed(2), discount: (discount * rate).toFixed(2) })); }

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.

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:

import createCachedSelector from 're-reselect'; const resolveCurrencyCacheKey = (state, props = {}) => { let { currency } = props; const CURRENCIES = ['USD', 'GBP', 'EUR', 'YEN']; currency = (currency && typeof currency === 'string') ? currency.toUpperCase() : 'USD'; return `currency:${ CURRENCIES.includes(currency) ? currency : 'USD' }`; } const getSelectedItems = createCachedSelector( [getItems, getSelected], (items, selected) => items.filter(item => selected.includes(item.id)) )(resolveCurrencyCacheKey); const getSelectedItemsAmount = createCachedSelector( [getSelectedItems], (items) => items.reduce((total, item) => { return total + Number(item.price) - ( Number(item.discount) || 0 ) }, 0).toFixed(2) )(resolveCurrencyCacheKey);

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.

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:

import createSagaMiddleware from 'redux-saga'; import { createStore, applyMiddleware } from 'redux'; import rootSaga from './sagas'; // Create Saga middleware // and apply it as a store enhancement const sagaMiddleware = createSagaMiddleware(); const createStoreWithSaga = applyMiddleware(sagaMiddleware)(createStore); // Initial store state const INITIAL_STATE = { photo: null, error: null, fetching: false }; // Reducer for the store const reducer = (state, action) => { switch (action.type) { case 'PHOTO_FETCH_REQUESTED': return { ...state, fetching: true }; case 'PHOTO_FETCH_SUCCESSFUL': return { ...state, fetching: false, error: null, photo: action.photo }; case 'PHOTO_FETCH_FAILED': return { ...state, fetching: false, error: action.error }; default: return state; } }; // Create the store with Saga enhancement const store = createStoreWithSaga(reducer, INITIAL_STATE); // Run the root saga through the middleware sagaMiddleware.run(rootSaga); export default store;

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.

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:

function* infiniteGenerator () { for (let i = 1; true; i++) { yield i; } }

Now here is what the sagas.js file containing the root saga looks like:

import { call, put, takeLatest, fork } from 'redux-saga/effects'; // Simple helper to test for plain JavaScript objects const _isPlainObject = value => { return Object.prototype.toString.call(value) === '[object Object]'; } // Fetch a photo by ID from the Picsum API const fetchPhoto = photoId => { return fetch(`https://picsum.photos/list`) .then(response => response.json()) .then(photos => photos.find(photo => photo.id == photoId)); } // Worker Saga for handling async photo fetching function* photoFetchWorkerSaga (action) { try { const photo = yield call(fetchPhoto, action.payload.id); if (_isPlainObject(photo)) { yield put({ type: 'PHOTO_FETCH_SUCCESSFUL', photo }); } else { yield put({ type: 'PHOTO_FETCH_FAILED', error: 'Photo not found.' }); } } catch (e) { yield put({ type: 'PHOTO_FETCH_FAILED', error: e.message }); } } // Saga that looks for latest photo fetch request // and triggers the worker export default function* rootSaga() { yield takeLatest('PHOTO_FETCH_REQUESTED', photoFetchWorkerSaga); }

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.

Redux-Saga effects

Here is a brief list of some of the effects provided by the redux-saga package:

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

— 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

— Dispatches a Redux action fork — Runs the passed function in a non-blocking way

— Runs the passed function in a non-blocking way take — Pauses the saga until the specified Redux action is received

— Pauses the saga until the specified Redux action is received takeEvery — Returns result for every call triggered for the specified Redux action

— 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

— 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 epics

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:

import { mapTo } from 'rxjs/operators'; import { ofType } from 'redux-observable'; const pingEpic = action$ => { return action$.pipe( ofType('PING'), mapTo({ type: 'PONG' }) ); }

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.

Middleware setup

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:

import { createStore, applyMiddleware } from 'redux'; import { createEpicMiddleware } from 'redux-observable'; import rootEpic from './epics'; // Create Epic middleware // and apply it as a store enhancement const epicMiddleware = createEpicMiddleware(); const createStoreWithEpic = applyMiddleware(epicMiddleware)(createStore); // Initial store state const INITIAL_STATE = { photo: null, error: null, fetching: false }; // Reducer for the store const reducer = (state, action) => { switch (action.type) { case 'PHOTO_FETCH_REQUESTED': return { ...state, fetching: true }; case 'PHOTO_FETCH_SUCCESSFUL': return { ...state, fetching: false, error: null, photo: action.photo }; case 'PHOTO_FETCH_FAILED': return { ...state, fetching: false, error: action.error }; default: return state; } }; // Create the store with Epic enhancement const store = createStoreWithEpic(reducer, INITIAL_STATE); // Run the root epic through the middleware epicMiddleware.run(rootEpic); export default store;

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:

import { of } from 'rxjs'; import { ajax } from 'rxjs/ajax'; import { combineEpics, ofType } from 'redux-observable'; import { map, mergeMap, catchError } from 'rxjs/operators'; // Simple helper to test for plain JavaScript objects const _isPlainObject = value => { return Object.prototype.toString.call(value) === '[object Object]'; }; const photoFetchEpic = (action$, state$) => { return action$.pipe( // Listen for only 'PHOTO_FETCH_REQUESTED' actions ofType('PHOTO_FETCH_REQUESTED'), // Map action to emit AJAX request mergeMap(action => ajax.getJSON('https://picsum.photos/list').pipe( map(photos => photos.find(({ id }) => id === action.payload.photo_id)), map(photo => _isPlainObject(photo) ? { type: 'PHOTO_FETCH_SUCCESSFUL', photo } : { type: 'PHOTO_FETCH_FAILED', error: 'Photo not found.' } ), // handle AJAX error catchError(err => of({ type: 'PHOTO_FETCH_FAILED', error: err.message })) ) ) ); }; // Create root epic by combining all other epics const rootEpic = combineEpics(photoFetchEpic); export default rootEpic;

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:

import { of } from 'rxjs'; import { ajax } from 'rxjs/ajax'; import { combineEpics, ofType } from 'redux-observable'; import { map, take, retry, debounceTime, switchMap, catchError } from 'rxjs/operators'; const photoFetchEpic = (action$, state$) => { return action$.pipe( // Listen for only 'PHOTO_FETCH_REQUESTED' actions ofType('PHOTO_FETCH_REQUESTED'), // Debounce requests within a 500ms time frame debounceTime(500), // Use switchMap instead of mergeMap to ensure that // in-flight AJAX requests can be terminated switchMap(action => ajax.getJSON('https://picsum.photos/list').pipe( map(photos => photos.find(({ id }) => id === action.payload.photo_id)), map(photo => _isPlainObject(photo) ? { type: 'PHOTO_FETCH_SUCCESSFUL', photo } : { type: 'PHOTO_FETCH_FAILED', error: 'Photo not found.' } ), // Retry the request a maximum of 3 times on failure retry(3), catchError(err => of({ type: 'PHOTO_FETCH_FAILED', error: err.message })) ) ), // Take only the first 10 photos fetched successfully // and ignore subsequent requests take(10) ); };

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:

{ "id": 123456, "title": "Robots serve humans food in a Nigerian restaurant", "body": "...", "comments": [ { "id": 123, "comment": "Interesting" }, { "id": 124, "comment": "Could this be real..." } ], "relatedFeeds": [ { "id": 123457, "title": "A tech company suffers losses from software hack" }, { "id": 123458, "title": "Automobile giant acquired by German motor company" } ] }

We can define the schema for this JSON data using Normalizr as follows:

import { schema } from 'normalizr'; const comment = new schema.Entity('comments'); const relatedFeed = new schema.Entity('relatedFeeds'); const feed = new schema.Entity('feeds', { comments: [comment], relatedFeeds: [relatedFeed] });

After creating the schema, we can use it to normalize the JSON data as follows:

import { schema, normalize } from 'normalizr'; const JSON_DATA = { // ... the JSON data here }; const normalizedData = normalize(JSON_DATA, feed);

Here is what the normalized data will look like:

{ "result": 123456, "entities": { "comments": { "123": { "id": 123, "comment": "Interesting" }, "124": { "id": 124, "comment": "Could this be real..." } }, "relatedFeeds": { "123457": { "id": 123457, "title": "A tech company suffers losses from software hack" }, "123458": { "id": 123458, "title": "Automobile giant acquired by German motor company" } }, "feeds": { "123456": { "id": 123456, "title": "Robots serve humans food in a Nigerian restaurant", "body": "...", "comments": [ 123, 124 ], "relatedFeeds": [ 123457, 123458 ] } } } }

Conclusion

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.

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…

Full visibility into production React apps Debugging React applications can be difficult, especially when users experience issues that are difficult to reproduce. If you’re interested in monitoring and tracking Redux state, automatically surfacing JavaScript errors, and tracking slow network requests and component load time, Debugging React applications can be difficult, especially when users experience issues that are difficult to reproduce. If you’re interested in monitoring and tracking Redux state, automatically surfacing JavaScript errors, and tracking slow network requests and component load time, try LogRocket LogRocket is like a DVR for web apps, recording literally everything that happens on your React app. Instead of guessing why problems happen, you can aggregate and report on what state your application was in when an issue occurred. LogRocket also monitors your app's performance, reporting with metrics like client CPU load, client memory usage, and more. The LogRocket Redux middleware package adds an extra layer of visibility into your user sessions. LogRocket logs all actions and state from your Redux stores. Modernize how you debug your React apps — start monitoring for free.