⚙️ Redux Middleware & Data Optimization

Day 31: Master Advanced Redux Patterns

📚 Middleware Kya Hai?

Middleware: A function that sits between dispatching an action and the moment it reaches the reducer. It provides a third-party extension point between dispatching an action and the reducer receiving it, allowing you to intercept, modify, delay, or even stop actions.

Simple shabdon mein: Middleware ek "middleman" hai jo action aur reducer ke beech mein baithta hai. Jaise airport pe security check hota hai passengers ke liye, waise hi middleware har action ko check kar sakta hai, modify kar sakta hai, ya extra kaam kar sakta hai! 🛂

Real-world analogy: Socho aap ek letter bhej rahe ho. Post office (middleware) letter ko receive karta hai, stamp lagata hai, tracking add karta hai, aur phir deliver karta hai. Middleware bhi aise hi actions ke saath kaam karta hai!

🔄 Middleware Flow Visualization

🎬 Action

Dispatch

→

⚙️ Middleware 1

Logger

→

⚙️ Middleware 2

Thunk

→

⚙️ Middleware 3

Error Handler

→

📦 Reducer

State Update

// Middleware Signature (Curried Function) const middleware = (store) => (next) => (action) => { // store: Redux store ka access // next: Agle middleware ya reducer ko call karne ke liye // action: Current action object // Before reducer console.log('Action dispatched:', action); // Call next middleware/reducer const result = next(action); // After reducer console.log('New state:', store.getState()); return result; };

1️⃣ Custom Middleware Banana

Custom Middleware: User-defined middleware functions that implement specific logic for handling actions, such as logging, analytics, crash reporting, or custom business logic.

Kyun banana? Redux mein built-in middleware limited hain. Aapko apne specific use cases ke liye custom middleware banana padta hai - jaise analytics tracking, API calls, validation, etc.

📝 Logger Middleware

Har action aur state change ko log karo

Debugging

🚨 Error Handler

Errors ko catch aur report karo

Error Tracking

📊 Analytics

User actions ko track karo

Monitoring

⏱️ Performance

Action execution time measure karo

Optimization

🎯 Live Demo: Custom Middleware in Action

Neeche buttons click karo aur dekho middleware kaise kaam karta hai!

[00:00:00] ✓ Middleware system initialized
// 1. Logger Middleware - Har action ko log karo const loggerMiddleware = (store) => (next) => (action) => { console.group(`Action: ${action.type}`); console.log('Previous State:', store.getState()); console.log('Action:', action); const result = next(action); console.log('Next State:', store.getState()); console.groupEnd(); return result; }; // 2. Performance Middleware - Execution time measure karo const performanceMiddleware = (store) => (next) => (action) => { const start = performance.now(); const result = next(action); const end = performance.now(); const duration = end - start; if (duration > 10) { console.warn(`Slow action: ${action.type} took ${duration}ms`); } return result; }; // 3. Error Handler Middleware - Errors ko catch karo const errorMiddleware = (store) => (next) => (action) => { try { return next(action); } catch (error) { console.error('Error in action:', action.type, error); // Error reporting service ko bhejo reportError(error, { action, state: store.getState() }); // Error action dispatch karo store.dispatch({ type: 'ERROR_OCCURRED', payload: error.message }); return error; } }; // 4. Analytics Middleware - User actions track karo const analyticsMiddleware = (store) => (next) => (action) => { // Track specific actions if (action.type.startsWith('USER_')) { trackEvent({ event: action.type, properties: action.payload, timestamp: new Date().toISOString() }); } return next(action); }; // Apply middleware to store import { applyMiddleware, createStore } from 'redux'; const store = createStore( rootReducer, applyMiddleware( loggerMiddleware, performanceMiddleware, errorMiddleware, analyticsMiddleware ) );
Middleware ka order matter karta hai! Jo pehle apply hoga, wo pehle execute hoga. Logger ko usually sabse pehle rakhte hain taaki sab kuch log ho sake.

2️⃣ Redux Thunk - Deep Dive

Redux Thunk: A middleware that allows you to write action creators that return a function instead of an action object. The thunk can be used to delay the dispatch of an action, or to dispatch only if a certain condition is met.

Kya hai Thunk? Thunk ek function hai jo function return karta hai! 🤯 Normally action creators objects return karte hain, but thunk se aap functions return kar sakte ho jo async operations kar sakein.

Kyun chahiye? Redux mein reducers pure functions hone chahiye - no side effects, no async code. Thunk middleware se aap async operations (API calls, timers) kar sakte ho!

// Redux Thunk Source Code (Simplified) const thunk = (store) => (next) => (action) => { // Agar action ek function hai, toh use call karo if (typeof action === 'function') { return action(store.dispatch, store.getState); } // Warna normal action ki tarah handle karo return next(action); }; // Simple Thunk Example const incrementAsync = () => { return (dispatch, getState) => { setTimeout(() => { dispatch({ type: 'INCREMENT' }); }, 1000); }; }; // API Call Thunk const fetchUsers = () => { return async (dispatch, getState) => { // Loading state dispatch({ type: 'FETCH_USERS_REQUEST' }); try { const response = await fetch('/api/users'); const data = await response.json(); // Success dispatch({ type: 'FETCH_USERS_SUCCESS', payload: data }); } catch (error) { // Error dispatch({ type: 'FETCH_USERS_FAILURE', payload: error.message }); } }; };

🎯 Live Demo: Thunk in Action

Simple Async Thunk

1 second delay ke baad increment hoga

0
Counter Value

API Call Thunk

Fetch users from API

Conditional Thunk

Sirf even numbers pe increment hoga

0
Counter (Even Only)
// Advanced Thunk Patterns // 1. Conditional Dispatch - State check karke dispatch karo const incrementIfOdd = () => { return (dispatch, getState) => { const { counter } = getState(); if (counter % 2 === 1) { dispatch({ type: 'INCREMENT' }); } }; }; // 2. Chained Thunks - Ek thunk dusre thunk ko dispatch kare const fetchUserAndPosts = (userId) => { return async (dispatch) => { // First fetch user await dispatch(fetchUser(userId)); // Then fetch their posts await dispatch(fetchUserPosts(userId)); }; }; // 3. Cancellable Thunk - AbortController use karo const fetchWithCancel = (url) => { let abortController = new AbortController(); const thunk = async (dispatch) => { try { const response = await fetch(url, { signal: abortController.signal }); const data = await response.json(); dispatch({ type: 'SUCCESS', payload: data }); } catch (error) { if (error.name === 'AbortError') { console.log('Request cancelled'); } } }; thunk.cancel = () => abortController.abort(); return thunk; }; // 4. Retry Logic - Failed requests ko retry karo const fetchWithRetry = (url, maxRetries = 3) => { return async (dispatch) => { for (let i = 0; i < maxRetries; i++) { try { const response = await fetch(url); const data = await response.json(); dispatch({ type: 'SUCCESS', payload: data }); return; } catch (error) { if (i === maxRetries - 1) { dispatch({ type: 'FAILURE', payload: error }); } await new Promise(r => setTimeout(r, 1000 * (i + 1))); } } }; };
🎯 Thunk Best Practices:
  • Always handle loading, success, and error states
  • Use try-catch for error handling
  • Keep thunks focused on one responsibility
  • Return promises for better composition
  • Use getState() sparingly - prefer passing data as arguments

3️⃣ Logging & Error Handling Middleware

Logging Middleware: Middleware that records actions, state changes, and other events for debugging and monitoring purposes. Error handling middleware catches and processes errors that occur during action processing.

Production mein kyun zaroori? Development mein toh console.log se kaam chal jata hai, but production mein proper logging aur error tracking chahiye. Jab user ko error aaye, aapko pata hona chahiye kya hua!

📊 Action Logging

Har action ko timestamp ke saath log karo

const logger = store => next => action => { console.log('dispatching', action); return next(action); };

🚨 Error Tracking

Errors ko catch aur report karo

const errorHandler = store => next => action => { try { return next(action); } catch (err) { reportError(err); } };

⏱️ Performance Monitor

Slow actions ko identify karo

const perf = store => next => action => { const start = Date.now(); const result = next(action); console.log(Date.now() - start); return result; };

📈 State Diff

State changes ko visualize karo

const diff = store => next => action => { const prev = store.getState(); const result = next(action); const curr = store.getState(); console.log(diff(prev, curr)); return result; };
// Production-Ready Logger Middleware const createLogger = (options = {}) => { const { collapsed = true, duration = true, timestamp = true, colors = { title: '#000', prevState: '#9E9E9E', action: '#03A9F4', nextState: '#4CAF50', error: '#F20404' } } = options; return (store) => (next) => (action) => { const startTime = performance.now(); const prevState = store.getState(); // Create log group const logGroup = collapsed ? console.groupCollapsed : console.group; const time = new Date().toLocaleTimeString(); const title = `action ${action.type} @ ${time}`; try { logGroup(`%c ${title}`, `color: ${colors.title}; font-weight: bold`); } catch (e) { console.log(title); } // Log previous state console.log( '%c prev state', `color: ${colors.prevState}; font-weight: bold`, prevState ); // Log action console.log( '%c action', `color: ${colors.action}; font-weight: bold`, action ); // Execute action const result = next(action); const nextState = store.getState(); // Log next state console.log( '%c next state', `color: ${colors.nextState}; font-weight: bold`, nextState ); // Log duration if (duration) { const endTime = performance.now(); const took = endTime - startTime; console.log(`%c duration: ${took.toFixed(2)}ms`, 'color: #999'); } console.groupEnd(); return result; }; }; // Advanced Error Handler with Reporting const createErrorHandler = (errorReporter) => { return (store) => (next) => (action) => { try { return next(action); } catch (error) { // Log error details console.error('Error in action:', { action, error, state: store.getState(), timestamp: new Date().toISOString() }); // Report to error tracking service (Sentry, Bugsnag, etc.) if (errorReporter) { errorReporter.captureException(error, { extra: { action: action.type, payload: action.payload, state: store.getState() } }); } // Dispatch error action store.dispatch({ type: '@@ERROR/GLOBAL_ERROR', payload: { message: error.message, stack: error.stack, action: action.type } }); // Re-throw in development if (process.env.NODE_ENV === 'development') { throw error; } return error; } }; }; // Usage import * as Sentry from '@sentry/browser'; const store = configureStore({ reducer: rootReducer, middleware: (getDefaultMiddleware) => getDefaultMiddleware() .concat(createLogger()) .concat(createErrorHandler(Sentry)) });

4️⃣ Reselect - Optimized Selectors

Reselect: A library for creating memoized selector functions. Selectors can compute derived data, allowing Redux to store the minimal possible state while efficiently computing derived values on-demand.

Problem kya hai? Jab bhi state change hoti hai, sabhi connected components re-render hote hain. Agar aap complex calculations kar rahe ho (filtering, sorting, aggregating), toh ye calculations har render pe dobara hoti hain - bahut slow! 🐌

Solution: Reselect! Ye selectors ko "memoize" karta hai - matlab agar input same hai, toh cached result return karta hai. Calculation dobara nahi hoti! ⚡

❌ Without Memoization

Every render:

  • Filter array
  • Sort results
  • Calculate totals
Slow 🐌

✅ With Memoization

Only when input changes:

  • Check cache
  • Return cached result
  • No recalculation!
Fast ⚡

🎯 Live Demo: Reselect Performance

Dekho memoization se kitna performance improve hota hai!

0
Without Memo (ms)
0
With Memo (ms)
0
Performance Gain (%)

Execution Time Comparison

Without Memoization:

0ms

With Memoization:

0ms
// Reselect Basic Usage import { createSelector } from 'reselect'; // Input selectors - Simple functions const selectTodos = (state) => state.todos; const selectFilter = (state) => state.filter; // Memoized selector - Complex computation const selectFilteredTodos = createSelector( [selectTodos, selectFilter], // Input selectors (todos, filter) => { // Result function console.log('Computing filtered todos...'); switch (filter) { case 'COMPLETED': return todos.filter(t => t.completed); case 'ACTIVE': return todos.filter(t => !t.completed); default: return todos; } } ); // Use in component const todos = useSelector(selectFilteredTodos); // Agar todos aur filter same hain, cached result milega!
// Advanced Reselect Patterns // 1. Chaining Selectors const selectCompletedTodos = createSelector( [selectTodos], (todos) => todos.filter(t => t.completed) ); const selectCompletedTodoCount = createSelector( [selectCompletedTodos], (completedTodos) => completedTodos.length ); // 2. Selector with Multiple Inputs const selectVisibleTodosWithStats = createSelector( [selectTodos, selectFilter, selectSearchTerm], (todos, filter, searchTerm) => { let filtered = todos; // Apply filter if (filter !== 'ALL') { filtered = filtered.filter(t => filter === 'COMPLETED' ? t.completed : !t.completed ); } // Apply search if (searchTerm) { filtered = filtered.filter(t => t.text.toLowerCase().includes(searchTerm.toLowerCase()) ); } return { todos: filtered, total: filtered.length, completed: filtered.filter(t => t.completed).length }; } ); // 3. Parametric Selectors (with props) const makeSelectTodoById = () => { return createSelector( [selectTodos, (state, todoId) => todoId], (todos, todoId) => todos.find(t => t.id === todoId) ); }; // Usage in component const selectTodoById = useMemo(makeSelectTodoById, []); const todo = useSelector(state => selectTodoById(state, todoId)); // 4. Selector with Expensive Computation const selectSortedAndGroupedTodos = createSelector( [selectTodos], (todos) => { // Expensive operations const sorted = [...todos].sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt) ); const grouped = sorted.reduce((acc, todo) => { const date = new Date(todo.createdAt).toDateString(); if (!acc[date]) acc[date] = []; acc[date].push(todo); return acc; }, {}); return grouped; } ); // 5. Selector Factory for Multiple Instances const makeSelectUserTodos = () => { return createSelector( [selectTodos, (state, userId) => userId], (todos, userId) => todos.filter(t => t.userId === userId) ); };
Scenario Without Reselect With Reselect State unchanged Recalculates every time Returns cached result Unrelated state change Recalculates unnecessarily Returns cached result Input changed Recalculates Recalculates (expected) Performance Slow with complex logic Fast with memoization Memory No extra memory Stores last result
Reselect sirf last result cache karta hai. Agar aapko multiple results cache karne hain (different props ke liye), toh re-reselect library use karo jo LRU cache provide karti hai!

5️⃣ Middleware & Optimization Best Practices

1. ✅ Middleware Order

Sahi order mein apply karo

// Correct order applyMiddleware( logger, // First thunk, // Then errorHandler // Last )

2. ✅ Conditional Middleware

Development vs Production

const middleware = [thunk]; if (process.env.NODE_ENV === 'development') { middleware.push(logger); }

3. ✅ Selector Composition

Chote selectors se bade banao

const selectA = state => state.a; const selectB = createSelector( [selectA], a => compute(a) );

4. ✅ Avoid Over-Memoization

Simple selectors ko memoize mat karo

// ❌ Unnecessary const selectUser = createSelector( state => state.user, user => user ); // ✅ Simple is better const selectUser = state => state.user;
🎯 Performance Optimization Checklist:
  • ✅ Use Reselect for expensive computations
  • ✅ Keep middleware lightweight
  • ✅ Avoid synchronous heavy operations in middleware
  • ✅ Use proper error boundaries
  • ✅ Monitor middleware execution time
  • ✅ Cache API responses when possible
  • ✅ Debounce/throttle frequent actions

🎯 Interview Questions (Basic to Advanced)

Click on each question to reveal the answer

Q1. Redux Middleware kya hai aur kaise kaam karta hai? (Basic) ▼

Answer:

Middleware: Ek function jo action dispatch hone aur reducer tak pahunchne ke beech mein execute hota hai.

Signature:

const middleware = (store) => (next) => (action) => { // Before reducer console.log('Action:', action); // Call next middleware/reducer const result = next(action); // After reducer console.log('New State:', store.getState()); return result; };

Kaise Kaam Karta Hai:

  1. store: Redux store ka access (getState, dispatch)
  2. next: Agle middleware ya reducer ko call karne ke liye
  3. action: Current action object

Use Cases:

  • Logging (actions aur state changes)
  • Async operations (API calls)
  • Error handling
  • Analytics tracking
  • Action transformation

Hinglish: Middleware ek checkpoint hai jo har action ko check karta hai. Aap action ko modify kar sakte ho, delay kar sakte ho, ya extra kaam kar sakte ho before reducer!

Q2. Redux Thunk kya hai aur kab use karte hain? (Basic) ▼

Answer:

Redux Thunk: Ek middleware jo aapko action creators se functions return karne deta hai instead of plain objects.

Normal Action Creator:

// Returns object const increment = () => ({ type: 'INCREMENT' });

Thunk Action Creator:

// Returns function const incrementAsync = () => { return (dispatch, getState) => { setTimeout(() => { dispatch({ type: 'INCREMENT' }); }, 1000); }; };

Kab Use Karein:

  • API Calls: Async data fetching
  • Delayed Actions: setTimeout/setInterval
  • Conditional Dispatch: State check karke dispatch
  • Multiple Dispatches: Ek action se multiple dispatches

Benefits:

  • Async logic ko action creators mein rakh sakte ho
  • Components clean rahte hain
  • Reusable async logic
  • Access to dispatch and getState

Hinglish: Thunk se aap functions return kar sakte ho jo async operations kar sakein. Ye API calls aur delayed actions ke liye perfect hai!

Q3. Custom middleware kaise banate hain? Example ke saath explain karo (Intermediate) ▼

Answer:

Custom Middleware Structure: Three nested functions (currying pattern)

// Template const myMiddleware = (store) => (next) => (action) => { // Your logic here return next(action); }; // Example 1: Logger Middleware const logger = (store) => (next) => (action) => { console.group(action.type); console.log('Previous State:', store.getState()); console.log('Action:', action); const result = next(action); console.log('Next State:', store.getState()); console.groupEnd(); return result; }; // Example 2: Analytics Middleware const analytics = (store) => (next) => (action) => { // Track specific actions if (action.type.startsWith('USER_')) { trackEvent({ event: action.type, userId: store.getState().user.id, timestamp: Date.now() }); } return next(action); }; // Example 3: API Middleware const api = (store) => (next) => (action) => { if (action.type !== 'API_REQUEST') { return next(action); } const { endpoint, method, body, onSuccess, onError } = action.payload; fetch(endpoint, { method, body: JSON.stringify(body) }) .then(res => res.json()) .then(data => store.dispatch(onSuccess(data))) .catch(error => store.dispatch(onError(error))); }; // Apply middleware const store = createStore( reducer, applyMiddleware(logger, analytics, api) );

Key Points:

  • Always call next(): Warna action reducer tak nahi pahunchega
  • Return result: next() ka result return karo
  • Order matters: Middleware order important hai
  • Access store: getState() aur dispatch() use kar sakte ho

Hinglish: Custom middleware banana easy hai - teen nested functions banao aur apna logic add karo. next() call karna mat bhoolna!

Q4. Reselect kya hai aur memoization kaise kaam karta hai? (Intermediate) ▼

Answer:

Reselect: Library for creating memoized selectors jo derived data efficiently compute karti hai.

Problem Without Memoization:

// Har render pe recalculate hota hai const TodoList = () => { const todos = useSelector(state => state.todos); // Expensive computation - har render pe! const sortedTodos = todos .filter(t => !t.completed) .sort((a, b) => b.priority - a.priority); return /* render */; };

Solution With Reselect:

import { createSelector } from 'reselect'; // Input selector const selectTodos = state => state.todos; // Memoized selector const selectSortedActiveTodos = createSelector( [selectTodos], (todos) => { console.log('Computing...'); // Sirf jab todos change ho return todos .filter(t => !t.completed) .sort((a, b) => b.priority - a.priority); } ); // Use in component const sortedTodos = useSelector(selectSortedActiveTodos); // Agar todos same hain, cached result milega!

How Memoization Works:

  1. Selector ko call karo with current state
  2. Input selectors run hote hain
  3. Check: Kya inputs previous call se same hain?
  4. If YES: Return cached result (fast!)
  5. If NO: Run computation, cache result, return

Benefits:

  • Performance: Unnecessary recalculations avoid
  • Composable: Selectors ko chain kar sakte ho
  • Testable: Pure functions, easy to test
  • Encapsulation: State shape ko hide kar sakte ho

When to Use:

  • Filtering/sorting large arrays
  • Complex calculations
  • Aggregating data from multiple slices
  • Transforming data for UI

Hinglish: Reselect ek smart cache hai. Agar input same hai toh calculation dobara nahi hoti - cached result return hota hai. Bahut fast! ⚡

Q5. Middleware execution order kaise kaam karta hai? Example se explain karo (Advanced) ▼

Answer:

Middleware Chain: Middleware ek chain ki tarah execute hote hain - pehle se last tak, phir wapas last se pehle tak.

// Three middleware const middleware1 = store => next => action => { console.log('1: Before'); const result = next(action); console.log('1: After'); return result; }; const middleware2 = store => next => action => { console.log('2: Before'); const result = next(action); console.log('2: After'); return result; }; const middleware3 = store => next => action => { console.log('3: Before'); const result = next(action); console.log('3: After'); return result; }; // Apply in order applyMiddleware(middleware1, middleware2, middleware3); // When action is dispatched: // Output: // 1: Before // 2: Before // 3: Before // [Reducer executes] // 3: After // 2: After // 1: After

Execution Flow:

  1. Action dispatch hota hai
  2. Middleware 1 execute hota hai (before next)
  3. Middleware 2 execute hota hai (before next)
  4. Middleware 3 execute hota hai (before next)
  5. Reducer execute hota hai
  6. Middleware 3 complete hota hai (after next)
  7. Middleware 2 complete hota hai (after next)
  8. Middleware 1 complete hota hai (after next)

Practical Example:

// Correct order for common middleware applyMiddleware( logger, // 1. Log everything first crashReporter, // 2. Catch errors thunk, // 3. Handle async actions analytics // 4. Track after processing ); // Why this order? // - Logger sees all actions (even from thunks) // - Crash reporter catches all errors // - Thunk converts functions to actions // - Analytics tracks final actions

Common Mistakes:

  • ❌ Thunk after logger: Logger won't see thunk actions
  • ❌ Error handler last: Won't catch errors from other middleware
  • ❌ Not calling next(): Chain breaks, reducer never executes

Hinglish: Middleware ek onion ki tarah hai - bahar se andar jaate ho, phir andar se bahar aate ho. Order bahut important hai!

Q6. Production-ready error handling middleware kaise implement karein? (Advanced) ▼

Answer:

Production Error Handler: Comprehensive error handling with reporting, recovery, and user feedback.

const createErrorHandler = (config = {}) => { const { errorReporter = null, // Sentry, Bugsnag, etc. showUserError = true, logErrors = true, retryableActions = [] } = config; return (store) => (next) => (action) => { try { // Try to execute action return next(action); } catch (error) { // 1. Log error details if (logErrors) { console.error('Redux Error:', { action: action.type, payload: action.payload, error: error.message, stack: error.stack, state: store.getState(), timestamp: new Date().toISOString() }); } // 2. Report to error tracking service if (errorReporter) { errorReporter.captureException(error, { tags: { action: action.type, environment: process.env.NODE_ENV }, extra: { payload: action.payload, state: store.getState() }, level: 'error' }); } // 3. Dispatch error action store.dispatch({ type: '@@ERROR/GLOBAL_ERROR', payload: { message: error.message, action: action.type, recoverable: retryableActions.includes(action.type) }, error: true }); // 4. Show user-friendly error if (showUserError) { store.dispatch({ type: 'SHOW_NOTIFICATION', payload: { type: 'error', message: 'Something went wrong. Please try again.' } }); } // 5. Retry logic for specific actions if (retryableActions.includes(action.type)) { setTimeout(() => { console.log('Retrying action:', action.type); store.dispatch(action); }, 2000); } // 6. Don't throw in production if (process.env.NODE_ENV === 'development') { throw error; } return error; } }; }; // Usage with Sentry import * as Sentry from '@sentry/react'; Sentry.init({ dsn: 'your-sentry-dsn', environment: process.env.NODE_ENV }); const errorHandler = createErrorHandler({ errorReporter: Sentry, retryableActions: ['FETCH_DATA', 'SAVE_DATA'] }); const store = configureStore({ reducer: rootReducer, middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat(errorHandler) });

Key Features:

  • Comprehensive Logging: Action, state, timestamp
  • Error Reporting: Integration with Sentry/Bugsnag
  • User Feedback: Show friendly error messages
  • Retry Logic: Automatic retry for specific actions
  • Environment Aware: Different behavior for dev/prod
  • State Recovery: Dispatch recovery actions

Hinglish: Production error handler ko comprehensive hona chahiye - errors ko log karo, report karo, user ko batao, aur possible ho toh recover karo!

Q7. Reselect mein parametric selectors kaise banate hain? Factory pattern explain karo (Expert) ▼

Answer:

Problem: Agar aapko props ke basis pe selector banana hai (e.g., specific user ID), toh simple createSelector kaam nahi karega kyunki har component instance ko apna memoized selector chahiye.

Solution: Selector Factory Pattern

// ❌ Wrong: Shared selector (memoization breaks) const selectUserById = createSelector( [state => state.users, (state, userId) => userId], (users, userId) => users.find(u => u.id === userId) ); // Problem: Multiple components with different IDs // Component A: userId = 1 // Component B: userId = 2 // Memoization breaks because inputs keep changing! // ✅ Correct: Factory Pattern const makeSelectUserById = () => { return createSelector( [state => state.users, (state, userId) => userId], (users, userId) => { console.log('Computing user...'); return users.find(u => u.id === userId); } ); }; // Usage in component const UserProfile = ({ userId }) => { // Create selector instance for this component const selectUserById = useMemo(makeSelectUserById, []); // Use the selector const user = useSelector(state => selectUserById(state, userId) ); return <div>{user.name}</div>; };

Advanced: Multiple Parameters

// Factory with multiple parameters const makeSelectFilteredTodos = () => { return createSelector( [ state => state.todos, (state, userId) => userId, (state, userId, filter) => filter ], (todos, userId, filter) => { console.log('Computing filtered todos...'); let filtered = todos.filter(t => t.userId === userId); if (filter === 'completed') { filtered = filtered.filter(t => t.completed); } else if (filter === 'active') { filtered = filtered.filter(t => !t.completed); } return filtered; } ); }; // Usage const TodoList = ({ userId, filter }) => { const selectFilteredTodos = useMemo( makeSelectFilteredTodos, [] ); const todos = useSelector(state => selectFilteredTodos(state, userId, filter) ); return /* render */; };

Alternative: Re-Reselect (LRU Cache)

import createCachedSelector from 're-reselect'; // Automatically caches multiple results const selectUserById = createCachedSelector( [state => state.users, (state, userId) => userId], (users, userId) => users.find(u => u.id === userId) )( // Key selector - cache key (state, userId) => userId ); // No need for factory pattern! const user = useSelector(state => selectUserById(state, userId) );

When to Use Each:

Pattern Use Case
Simple Selector No props needed
Factory Pattern Few instances, simple caching
Re-Reselect Many instances, complex caching

Hinglish: Parametric selectors ke liye factory pattern use karo - har component instance ko apna memoized selector milega. Re-reselect library automatic LRU cache provide karti hai!

📝 Quick Summary

  • ✅ Middleware: Action aur reducer ke beech ka layer
  • ✅ Custom Middleware: Logging, analytics, error handling
  • ✅ Redux Thunk: Async operations ke liye functions return karo
  • ✅ Error Handling: Production-ready error tracking aur recovery
  • ✅ Reselect: Memoized selectors for performance
  • ✅ Middleware Order: Logger → Error → Thunk → Analytics
  • ✅ Optimization: Expensive computations ko memoize karo
  • ✅ Factory Pattern: Parametric selectors ke liye