Upgrade & Secure Your Future with DevOps, SRE, DevSecOps, MLOps!

We spend hours scrolling social media and waste money on things we forget, but won’t spend 30 minutes a day earning certifications that can change our lives.
Master in DevOps, SRE, DevSecOps & MLOps by DevOpsSchool!

Learn from Guru Rajesh Kumar and double your salary in just one year.


Get Started Now!

Redux Deep Dive: Complete Guide to State Management in Modern JavaScript Applications


What is Redux?

Redux is a predictable state container for JavaScript applications. It was created by Dan Abramov and Andrew Clark in 2015 to help developers manage the state of complex web applications in a more consistent and maintainable way. Redux is especially popular within the React ecosystem but is framework-agnostic and can be used with any UI layer or even plain JavaScript.

Core Idea

At its core, Redux centralizes an application’s state into a single immutable store, and the only way to modify the state is through dispatching actions that describe what happened. These actions are processed by reducers, which are pure functions that produce a new state based on the previous state and the action.

This architecture promotes unidirectional data flow, making the application easier to understand, debug, and test.

Redux’s Three Fundamental Principles:

  1. Single Source of Truth: The entire state of the application is kept in a single JavaScript object (the store).
  2. State is Read-Only: You cannot mutate the state directly. Instead, you dispatch actions describing what happened.
  3. Changes are Made with Pure Functions: Reducers are pure functions that take the previous state and an action and return the new state without side effects.

Major Use Cases of Redux

Redux is particularly suited for managing state in applications where the UI is dynamic and user interactions cause complex state transitions. Its primary use cases include:

1. Complex User Interfaces

Applications with numerous interactive components that share state — for example, dashboards, admin panels, or social media platforms — benefit greatly from Redux’s centralized state management.

2. State Sharing Across Components

In React apps, passing state through multiple layers of components (prop drilling) can get cumbersome. Redux solves this by making state accessible globally to components that need it.

3. Predictability & Debugging

Redux DevTools allow developers to inspect every action dispatched and every state change, rewind or replay state changes, and perform time-travel debugging, which is invaluable in complex apps.

4. Server-Side Rendering (SSR)

Redux enables you to initialize the app state on the server, send it to the client, and hydrate the app with the same state, ensuring consistency and better SEO.

5. Asynchronous State Changes

Using middleware like Redux Thunk or Redux Saga, Redux supports managing asynchronous events such as API calls, timers, and complex side effects.

6. Cross-Platform Applications

Redux is not limited to the web; it’s widely used in React Native apps and desktop apps built with Electron, enabling shared business logic across platforms.

7. Integration with Other JavaScript Frameworks

While Redux is tightly associated with React, it can be integrated with Angular, Vue, or vanilla JavaScript for consistent state management.


How Redux Works Along with Architecture

Redux’s architecture revolves around three core elements — Store, Actions, and Reducers — and a strict unidirectional data flow that keeps the application’s state predictable and traceable.

The Store

  • Single Store: Redux enforces a single store that holds the entire application state tree.
  • Methods:
    • getState(): Returns the current state.
    • dispatch(action): Sends an action to modify the state.
    • subscribe(listener): Registers listeners that respond to state changes.

Actions

  • Plain Objects: Actions are plain JavaScript objects describing what happened, always including a type property.
  • Payload: Actions can include additional data necessary for the state change.
  • Action Creators: Functions returning actions, improving code reusability.

Example:

{
  type: 'ADD_TODO',
  payload: { text: 'Learn Redux' }
}

Reducers

  • Pure Functions: Reducers take the current state and an action and return a new state without modifying the original.
  • Composability: Multiple reducers can be combined to manage slices of the state tree using combineReducers.

Example reducer:

function todoReducer(state = [], action) {
  switch (action.type) {
    case 'ADD_TODO':
      return [...state, action.payload];
    default:
      return state;
  }
}

Data Flow Diagram

  1. User interacts with the UI.
  2. UI dispatches an action.
  3. The store passes the action and current state to reducers.
  4. Reducers calculate and return new state.
  5. The store updates its state and notifies subscribers.
  6. UI components re-render with updated state.

Middleware Architecture

Middleware in Redux provides a third-party extension point between dispatching an action and the moment it reaches the reducer. It enables side effects, logging, crash reporting, and asynchronous actions.

Popular middleware:

  • Redux Thunk: Allows dispatching functions (thunks) for async logic.
  • Redux Saga: Uses ES6 generators for complex side effects.
  • Redux Logger: Logs every action and state change.

Basic Workflow of Redux

The lifecycle of Redux can be summarized in the following steps:

  1. Setup: Create a Redux store with reducers and apply middleware if needed.
  2. Dispatch: Components dispatch actions based on user input or lifecycle events.
  3. Reduce: Reducers receive the current state and the action, returning the new state.
  4. Update: Store updates its state immutably.
  5. Subscribe: React components or other listeners respond to state changes.
  6. Render: The UI re-renders to reflect the latest state.

Step-by-Step Getting Started Guide for Redux

Step 1: Install Dependencies

npm install redux react-redux

Step 2: Define Action Types and Creators

// actionTypes.js
export const INCREMENT = 'INCREMENT';
export const DECREMENT = 'DECREMENT';

// actions.js
import { INCREMENT, DECREMENT } from './actionTypes';

export const increment = () => ({ type: INCREMENT });
export const decrement = () => ({ type: DECREMENT });

Step 3: Create a Reducer

// reducer.js
import { INCREMENT, DECREMENT } from './actionTypes';

const initialState = { count: 0 };

export default function counterReducer(state = initialState, action) {
  switch (action.type) {
    case INCREMENT:
      return { count: state.count + 1 };
    case DECREMENT:
      return { count: state.count - 1 };
    default:
      return state;
  }
}

Step 4: Configure the Store

import { createStore } from 'redux';
import counterReducer from './reducer';

const store = createStore(counterReducer);
export default store;

Step 5: Integrate Store with React

Wrap your root component with the Provider to expose the store:

import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import App from './App';
import store from './store';

ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
);

Step 6: Connect React Components

Using React Redux hooks:

import React from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { increment, decrement } from './actions';

function Counter() {
  const count = useSelector(state => state.count);
  const dispatch = useDispatch();

  return (
    <div>
      <h1>{count}</h1>
      <button onClick={() => dispatch(increment())}>+</button>
      <button onClick={() => dispatch(decrement())}>-</button>
    </div>
  );
}

export default Counter;

Step 7: Use Middleware for Async Actions (Optional)

Install thunk:

npm install redux-thunk

Configure store with middleware:

import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import reducer from './reducer';

const store = createStore(reducer, applyMiddleware(thunk));

Create async action:

export const fetchData = () => async (dispatch) => {
  dispatch({ type: 'FETCH_START' });
  try {
    const response = await fetch('https://api.example.com/data');
    const data = await response.json();
    dispatch({ type: 'FETCH_SUCCESS', payload: data });
  } catch (error) {
    dispatch({ type: 'FETCH_ERROR', error });
  }
};

0 0 votes
Article Rating
Subscribe
Notify of
guest

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
0
Would love your thoughts, please comment.x
()
x