Author avatar

Gaurav Singhal

Where to Find Long-running Processes in a Redux App

Gaurav Singhal

  • Sep 15, 2020
  • 7 Min read
  • 133 Views
  • Sep 15, 2020
  • 7 Min read
  • 133 Views
Web Development
Front End Web Development
Client-side Frameworks
React

Introduction

Redux has complemented React in managing the complex state of an application and segmenting code into logical structures. Large enterprises using React are extensively using Redux for building scalable web apps.

As your app grows with time, it communicates with more external services, implements complex features, and diversifies in terms of functionalities. All these things constitute the long-running processes of your app. The ideal method for scaling your React+Redux app is structuring these processes so they can be reused without making significant changes in your architecture. This guide discusses certain methods that can help you understand where to put the long-running processes that govern the business logic in your React+Redux app.

Using Actions with Action Creators

Dispatching actions is one of the key concepts when using Redux. Your focus should be dispatching as many actions as necessary and then refactoring them using action creators. Most of the business logic in your app revolves around communication with backend APIs. Action creators not only reduce code redundancy, but you can also perform most of your work inside them. This way when the need for your app grows with time, you can use the same action creator to dispatch actions differently. For instance, consider the following dummy code that makes a GET request from a React component to get a user's information on login.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
axios.get(`https://backend/user/${user_id}`)
	.then(res=>{
		console.log(res.data)
    	if(res.data.success)
		dispatch({
			type:'SET_USER',
			payload:{
				user_type:res.data.user_type
			}
		})
	})
	.catch(err=>{
		console.log(err)
	})
js

You can use the same action when signing up a user on your web app.

1
export const SET_USER='SET_USER';
js

Refactor the above code as shown here.

1
2
3
4
5
6
7
8
...
dispatch({
    type:SET_USER,
    payload:{
        user_type:res.data.user_type
    }
})
...
js

Using Reducers

The next approach that helps you directly manage your global state is concentrating the work in your reducers. Since a reducer can directly interact with the state synchronously, you can call your components or change state both at the global and component levels conveniently. For instance, consider the following code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
const Users = (state = [], action) => {
  switch (action.type) {
    case 'AUTHENTICATE_USER':
      return [
        ...state,
        {
          user_id: action.payload.user_id,
          message: "Success",
          error: false
        }
      ]
    case 'ROLL_BACK':
     return [
        ...state,
        {
          user_id: null,
          message: "Wrong Credentials",
          error: true
        }
      ]
    default:
      return state
  }
}

export default Users
js

You have a reducer, Users, that fires an action and handles how the user is treated upon login. You can build a scalable reducer model using this approach by grouping multiple actions into reducers. For instance, if the user base of your app diversifies you can handle the different logic pertaining to a different kind of user in your reducer. This approach is hassle-free but breaks when you need to perform an async operation.

Using Middlewares

Redux offers you a wide range of middlewares that you can use right out of the box. These middlewares perform asynchronous tasks in your Redux app, and you can use them to tap into a promise or an action creator. Since any large scale app relies heavily on asynchronous operations like sending data to the server, retrieving data from it or making delete calls, etc. using middleware early in your app can help you explore how you can achieve asynchronous interaction with your store as your app gets complex with time. Thunk is one such middleware that you can use via the library Redux-Thunk. It has simple code and accesses your store conveniently while dealing with asynchronous actions. For instance, consider the following code:

1
2
3
4
5
6
7
export const getUser=()=>async (dispatch,getState)=>{
    const newUser=await fetchUser()
    dispatch({
        type:'GET_USER',
        payload: newUser
    })
}
js

Imagine you have a backend service that you consume inside the fetchUser() function. Using Thunk, you can return a promise from that function and use async and await to dispatch an action after a successful request. You can use the same logic for error handling, too. For example, you can build a general service for throwing exceptions from the backend. You can call the same service regardless of the kind of communication your app has with the server. This can help you save tons of redundant code for error handling as you consume more backend services.

Other Alternatives

Redux-Saga

Redux-Saga is a library like a Thunk that helps you manage asynchronous operations using ES6 generators. A generator in JavaScript is a combination of a function and an iterator. For a long-running process in your Redux app, using generators can help you step up your performance issues while still flooding it with more code. The only downside to this library is the steep learning curve.

Epics

Epics, a primitive of the library redux-observables, uses a different approach by focusing the flow around actions instead. It uses RxJS Observables for handling asynchronous operations. It looks and works like a simple function that takes in an action and gives out an action. Epics filter out the unnecessary stuff from your code in making API calls. Observables can prove to be really powerful in implementing features that depend on your backend services like autosuggestion and autocomplete. It also has a steep learning curve, especially for developers not so experienced with observables.

Redux-Loop

Redux-Loop is a middleware that hooks on to your reducer to execute side effects and dispatches the end result. The best part about using this library is that it offers extremely clean code, something that every developer fancies. The only disadvantage is that reducers now return more than just state, thereby increasing complexity.

Implementing Custom Middlewares

Custom middlewares are strategically great for scaling web apps over a large period. Depending on the current and anticipated needs of the app, you can create middleware that can perform all necessary operations such as intercepting actions, handling asynchronous operations, dispatching results, etc., exactly the way you want. It's a great way to structure your app that follows a defined pattern suited best according to your architecture. However, your knowledge of implementing a custom middleware should be sound if you don't want to spend hours debugging and refactoring your code.

Conclusion

Understanding where long-running processes live helps you develop a robust and scalable architecture for the frontend of your app. These processes affect the user experience as well as the performance of your app and determine how easily you can integrate new features and services to scale your app with time. The methods discussed in this guide allow you to develop features or modules that can be conveniently upgraded to fit broader needs with time.

3