State management with Redux-Saga

Pramuditha Jayasinghe
5 min readJul 5, 2024

--

Why State Management is needed for web development

A few years ago, when I was a full-stack .NET developer during my bachelor’s degree, I had to develop multiple ASP.NET web applications. At that time, I faced considerable challenges while dealing with data

  1. Maintaining data consistency: across multiple modules and different components such as tables and forms.
  2. Handling complex data flows: For example, I often needed to perform calculations based on temporary data sets. As a solution, I used temporary SQL tables to store these data sets and truncated them after calculations. However, due to database connection latency, I sometimes got incorrect values in calculations.
  3. Poor application performance: For instance, unnecessarily re-rendering components or repeatedly running backend functions to query data from the database to display in UI led to performance hits and unnecessary infrastructure costs, resulting in poor user experience.
  4. Limited maintainability and scalability of applications: When new requirements were requested, it was often difficult to accommodate them due to data inconsistencies and limitations of the current work. Sometimes, I had to rework previous modules extensively to align the application with new requirements.

These are just a few examples from my experience, and I’m pretty sure many developers face similar problems while working on front-end applications. These challenges led developers to seek a way to maintain a data store in frontend applications, which is where Redux was introduced.

What is Redux?

Redux is a predictable state container for JavaScript applications, primarily used with React. It addresses several pain points in large-scale application development:

  1. Single Source of Truth: Redux stores the entire application state in one central store, making it easier to track and debug.
  2. Predictable State Updates: State changes follow a strict unidirectional data flow, making the application’s behavior more predictable.
  3. Improved Maintainability: With a clear structure for state management, codebases become easier to maintain and scale.
  4. Developer Tools: Redux offers powerful developer tools for time-travel debugging and state inspection.
  5. Middleware Support: Redux allows the use of middleware for handling side effects and extending its capabilities.

By addressing these issues, Redux helps developers create more robust, maintainable, and scalable applications, especially as they grow in complexity.

What is Redux Saga

Redux Saga is a middleware library used to handle side effects in Redux applications. Side effects are operations that affect the outside world to perform some other tasks, such as data fetching I/O operations, API or graphQL integration, or any other asynchronous action.

Redux Saga uses “sagas,” which are generator functions (ES6 feature) that yield(pause and execute) objects to the redux-saga middleware. These objects describe the side effects, and redux-saga interprets them to perform the actual operations.

When/ Why you should use redux-saga

  • Efficient Side Effect Management: Redux Saga allows you to manage side effects in a more structured way, using generator functions to control the flow of side effects.
  • Better Code Organization: By separating side effect logic into sagas, you can keep your reducers pure and your action creators simple, leading to more maintainable and readable code.
  • Complex Asynchronous Logic: If your project involves complex asynchronous workflows, such as handling multiple dependent API calls, orchestrating multiple sequences of actions, or managing real-time updates

The following example demonstrates performing complex asynchronous calls inside a saga.

const user = yield call(axios.get, `/api/user/${action.payload.userId}`); 
yield put(fetchUserSuccess(user.data));

const orders = yield call(axios.get, `/api/user/${action.payload.userId}/orders`);

yield put(fetchOrdersSuccess(orders.data));

const orderDetails = yield all(orders.data.map(order =>
call(axios.get, `/api/orders/${order.id}`)));

yield put(fetchOrderDetailsSuccess(orderDetails.map(res => res.data)));
  • Handling Complex Scenarios: Redux Saga makes it easier to handle complex asynchronous workflows, such as debouncing, throttling, and parallel execution of side effects and it proveds utilities such as fork, join, cancel for concurrency management
  • Testing and Debugging: Sagas are easy to test because they are just generator functions. You can test the yielded effects sequence without performing any side effects.

How redux saga works

This diagram represents the flow of a Redux Saga application in a React application. Let’s break down each part of the diagram and explain how they interact:

1. React App

  • Role: The React application is the UI layer where the user interacts with the app.
  • Interaction: It dispatches actions to trigger state changes and uses selectors to read data from the Redux store.

2. Action Creators

  • Role: Functions that create action objects.
  • Interaction: These actions are dispatched from the React app to signal that something has happened (e.g., user interaction, API call initiation).

3. Watcher Saga

  • Role: Listens for dispatched actions.
  • Interaction: When an action is dispatched, the watcher saga checks if it matches any actions it is watching for and, if so, triggers the appropriate worker saga.

4. Worker Saga

  • Role: Handles side effects, such as API calls, data fetching, or other asynchronous operations.
  • Interaction: Executes the required side effects and then dispatches new actions with the results of those operations to reducers to update the store (e.g., success or failure).

5. Reducers

  • Role: Pure functions that take the current state and an action, then return a new state.
  • Interaction: Receives actions (dispatched by worker sagas or directly from the app), processes them and updates the state in the Redux store accordingly.

6. Redux Store

  • Role: Holds the entire state of the application.
  • Interaction: Provides state data to the React app via selectors and is updated by reducers in response to actions.

7. Selectors

  • Role: Functions that extract specific pieces of data from the Redux store.
  • Interaction: Used by the React app to read data from the store and reflect the state in the UI.

Execution flow explanation

  1. Dispatch Action: The React app dispatches an action using an action creator.
  2. Watcher Saga: The dispatched action is intercepted by the watcher saga, which listens for specific action types.
  3. Worker Saga: If the watcher saga detects the action it’s interested in, it triggers the corresponding worker saga to handle the side effect (e.g., API call).
  4. Side Effect: The worker saga performs the side effect and, upon completion, dispatches a new action with the results (e.g., success or failure).
  5. Reducers: The new action is processed by the reducers, which update the state in the Redux store based on the action’s payload.
  6. Redux Store Update: The updated state in the Redux store is then accessed by the React app using selectors.
  7. React App Update: The React app re-renders to reflect the new state.

Conclusion

Now I think you could get an understanding of why state management is needed in modern web development, what is redux, and why we should use redux-saga as middleware.
Please refer to the second part of this article How to configure Redux saga in a React Typescript project in Vite framework to get an understanding of actual implementation.

--

--

Pramuditha Jayasinghe

Software Engineer specialised in frontend system designing | tech enthusiast