Deep Dive into Complex Redux Concepts, Architecture & Real-World Patterns

Invalid Date (NaNy ago)

๐ŸŒช๏ธ Advanced Redux Patterns, Architecture & Complex Concepts

Redux is much more than a global state manager. At scale, it becomes a data-flow architecture with middleware pipelines, domain-based slices, complex selectors, and performance-centric designs. Let's dig deep.


๐Ÿง  Core Concepts Revisited

Redux operates on three principles:

  1. Single source of truth (Store)
  2. State is read-only (Actions)
  3. Changes via pure functions (Reducers)

But when applications grow:


๐Ÿ“ฆ Custom Redux Store from Scratch

import { createStore, applyMiddleware, compose } from "redux";
 
// Root reducer (pure function)
const rootReducer = (state = {}, action) => {
  switch (action.type) {
    case "ADD_TODO":
      return {
        ...state,
        todos: [...state.todos, action.payload],
      };
    default:
      return state;
  }
};
 
// Custom logging middleware
const logger = (store) => (next) => (action) => {
  console.log("Dispatching:", action);
  const result = next(action);
  console.log("Next State:", store.getState());
  return result;
};
 
// Composed store with dev tools
const store = createStore(
  rootReducer,
  { todos: [] },
  compose(
    applyMiddleware(logger),
    window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
  )
);

โš™๏ธ Advanced Middleware Patterns

๐Ÿ” Retry Middleware

const retryMiddleware =
  ({ dispatch }) =>
  (next) =>
  (action) => {
    if (!action.meta?.retry) return next(action);
 
    let attempts = 0;
    const maxAttempts = action.meta.retry;
 
    const attempt = () => {
      attempts++;
      const result = next(action);
      if (result.error && attempts < maxAttempts) {
        setTimeout(attempt, 1000);
      }
    };
 
    attempt();
  };

Use Case: For flaky API endpoints.


๐Ÿงฉ Dynamic Module Injection (Micro-Frontend Friendly)

function injectReducer(store, key, reducer) {
  store.asyncReducers[key] = reducer;
  store.replaceReducer(createRootReducer(store.asyncReducers));
}

Use Case: Dynamic pages loading their reducers on-demand.


๐Ÿงต Redux + Web Workers (Multithreading)

// worker.js
onmessage = (e) => {
  const result = heavyComputation(e.data);
  postMessage(result);
};
 
// In Redux middleware
const worker = new Worker("./worker.js");
 
const offloadMiddleware = (store) => (next) => (action) => {
  if (action.type !== "HEAVY_TASK") return next(action);
 
  worker.postMessage(action.payload);
  worker.onmessage = (e) => {
    store.dispatch({ type: "HEAVY_TASK_RESULT", payload: e.data });
  };
};

๐Ÿšฆ Redux Saga vs Thunks (Advanced Comparisons)

Saga

function* fetchUser() {
  try {
    const user = yield call(api.fetchUser);
    yield put({ type: "USER_SUCCESS", payload: user });
  } catch (e) {
    yield put({ type: "USER_ERROR", payload: e });
  }
}
 
function* rootSaga() {
  yield takeLatest("USER_FETCH", fetchUser);
}

Use Case:

Comparison:


๐Ÿง  Complex Selectors (Recomputing & Memoization)

import { createSelector } from "reselect";
 
const selectTodos = (state) => state.todos;
 
export const selectIncompleteTodos = createSelector([selectTodos], (todos) =>
  todos.filter((todo) => !todo.completed)
);

Optimization Use Case:


๐Ÿงฑ Feature-Based Redux Folder Structure

src/
 โ””โ”€โ”€ features/
     โ””โ”€โ”€ users/
         โ”œโ”€โ”€ UserSlice.js
         โ”œโ”€โ”€ UserSaga.js
         โ”œโ”€โ”€ UserSelectors.js
         โ””โ”€โ”€ UserService.js

Benefits: Isolation, scalability, dynamic injection.


๐Ÿ”ฅ Real-world Case: Redux + WebSocket

const socketMiddleware = (store) => (next) => (action) => {
  if (action.type === "WS_CONNECT") {
    const socket = new WebSocket(action.payload.url);
 
    socket.onmessage = (event) => {
      const message = JSON.parse(event.data);
      store.dispatch({ type: "WS_MESSAGE", payload: message });
    };
 
    store.dispatch({ type: "WS_CONNECTED" });
  }
  return next(action);
};

๐Ÿงญ Final Thoughts

Redux can handle massive, event-driven systems when: