🔄 Redux Complete Learning Guide

Master Redux with Interactive Visual Notes, Interview Q&A, and Comprehensive Definitions

🔍
📊 Learning Progress
0% Complete

🎨 What is Redux?

Core Concept

👶 Explain Like I'm 5

Imagine you have a big toy box (Store) where all your toys are kept. When you want to play with a toy, you tell your mom (Dispatch an Action). Your mom follows the rules (Reducer) to get the toy from the box and give it to you. Everyone in the family can see which toys are in the box (State), but only mom can put toys in or take them out following the rules!

📖 Simple Definition

Redux is a predictable state container for JavaScript apps. It helps you manage the "global" state - data that many parts of your app need to access and update. Think of it as a central database for your app's data!

Redux Data Flow

👁️ VIEW
User clicks button
→
📤 ACTION
What happened
→
⚙️ REDUCER
How to update
→
🗄️ STORE
New state
→
👁️ VIEW
UI updates

💡 When to Use Redux?

  • Large apps with lots of state that changes frequently
  • Many components need to access the same state
  • State needs to be shared across different parts of your app
  • You want predictable state updates with clear rules
  • You need time-travel debugging (undo/redo functionality)

⚠️ When NOT to Use Redux?

  • Small apps with simple state
  • State is only used in one component
  • You're just starting to learn React (learn React first!)
  • Your app doesn't have complex state management needs

🏗️ Core Concepts

Essential

1. Store 🗄️

👶 Simple Explanation

The Store is like a big treasure chest that holds ALL the data for your entire app. There's only ONE treasure chest, and everyone can look inside it, but only special people (reducers) can change what's inside!

Creating a Store with Redux Toolkit
import { configureStore } from '@reduxjs/toolkit';
import counterReducer from './counterSlice';

const store = configureStore({
  reducer: {
    counter: counterReducer,
    // Add more reducers here
  },
});

export default store;

2. Action 📤

👶 Simple Explanation

An Action is like a message or instruction. It's like saying "Hey, I want to add 1 to the counter!" or "I want to delete this item!". It describes WHAT happened, not HOW to do it.

Action Example
// Action is just a plain JavaScript object
const action = {
  type: 'counter/increment', // What happened
  payload: 5 // Optional data
};

// With Redux Toolkit, actions are created automatically!
import { createSlice } from '@reduxjs/toolkit';

const counterSlice = createSlice({
  name: 'counter',
  initialState: { value: 0 },
  reducers: {
    increment(state) {
      state.value += 1;
    },
    incrementByAmount(state, action) {
      state.value += action.payload;
    }
  }
});

// Actions are automatically created!
export const { increment, incrementByAmount } = counterSlice.actions;

3. Reducer ⚙️

👶 Simple Explanation

A Reducer is like a rule book. It says "IF this action happens, THEN do this to the state". It's a function that takes the old state and an action, and returns the NEW state. It's like a recipe - you follow the steps to make something new!

Reducer with Redux Toolkit
import { createSlice } from '@reduxjs/toolkit';

const todoSlice = createSlice({
  name: 'todos',
  initialState: [],
  reducers: {
    // Redux Toolkit allows "mutating" logic!
    // It uses Immer library internally
    addTodo(state, action) {
      state.push({
        id: Date.now(),
        text: action.payload,
        completed: false
      });
    },
    toggleTodo(state, action) {
      const todo = state.find(t => t.id === action.payload);
      if (todo) {
        todo.completed = !todo.completed;
      }
    },
    deleteTodo(state, action) {
      return state.filter(t => t.id !== action.payload);
    }
  }
});

export const { addTodo, toggleTodo, deleteTodo } = todoSlice.actions;
export default todoSlice.reducer;

4. Dispatch 🚀

👶 Simple Explanation

Dispatch is like pressing a button or sending a letter. When you want something to happen, you "dispatch" an action. It's the ONLY way to change the state in Redux!

Using Dispatch in React
import { useDispatch } from 'react-redux';
import { increment, incrementByAmount } from './counterSlice';

function Counter() {
  const dispatch = useDispatch();

  return (
    <div>
      <button onClick={() => dispatch(increment())}
        Add 1
      </button>
      
      <button onClick={() => dispatch(incrementByAmount(5))}
        Add 5
      </button>
    </div>
  );
}

5. Selector 🎯

👶 Simple Explanation

A Selector is like a magnifying glass that helps you look at specific parts of the treasure chest (store). Instead of looking at everything, you can use a selector to find exactly what you need!

Using Selectors
import { useSelector } from 'react-redux';

function Counter() {
  // Select specific data from the store
  const count = useSelector(state => state.counter.value);
  const todos = useSelector(state => state.todos);
  
  // You can also compute derived data
  const completedTodos = useSelector(state => 
    state.todos.filter(t => t.completed)
  );

  return (
    <div>
      <p>Count: {count}</p>
      <p>Completed: {completedTodos.length}</p>
    </div>
  );
}

6. Middleware 🔌

👶 Simple Explanation

Middleware is like a security guard or helper that stands between you sending a message (action) and it reaching the rule book (reducer). It can check the message, change it, or even stop it! It's useful for things like logging, making API calls, or handling errors.

Middleware Example - Logging
const loggerMiddleware = store => next => action => {
  console.log('Dispatching:', action);
  const result = next(action);
  console.log('Next state:', store.getState());
  return result;
};

// Redux Toolkit includes Redux Thunk middleware by default!
import { configureStore } from '@reduxjs/toolkit';

const store = configureStore({
  reducer: rootReducer,
  middleware: (getDefaultMiddleware) =>
    getDefaultMiddleware().concat(loggerMiddleware),
});

🛠️ Redux Toolkit Best Practices

Modern Redux

✨ Why Redux Toolkit?

Redux Toolkit is the official, recommended way to write Redux logic. It simplifies Redux code and includes best practices by default!

Best Practices Checklist

  • Use configureStore instead of createStore
  • Use createSlice to create reducers and actions together
  • Use createAsyncThunk for async logic
  • Use Immer for immutable updates (built into Redux Toolkit)
  • Keep reducers pure (no side effects)
  • Normalize complex/nested state
  • Use selectors to access state
  • Use createSelector for memoized selectors
  • Keep actions serializable (no functions, promises, etc.)
  • Use TypeScript for type safety
Complete Redux Toolkit Example
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';

// Async thunk for API calls
export const fetchUsers = createAsyncThunk(
  'users/fetchUsers',
  async () => {
    const response = await fetch('/api/users');
    return response.json();
  }
);

const usersSlice = createSlice({
  name: 'users',
  initialState: {
    entities: [],
    loading: 'idle',
    error: null
  },
  reducers: {
    userAdded(state, action) {
      state.entities.push(action.payload);
    },
    userUpdated(state, action) {
      const { id, changes } = action.payload;
      const user = state.entities.find(u => u.id === id);
      if (user) {
        Object.assign(user, changes);
      }
    }
  },
  extraReducers: (builder) => {
    builder
      .addCase(fetchUsers.pending, (state) => {
        state.loading = 'pending';
      })
      .addCase(fetchUsers.fulfilled, (state, action) => {
        state.loading = 'idle';
        state.entities = action.payload;
      })
      .addCase(fetchUsers.rejected, (state, action) => {
        state.loading = 'idle';
        state.error = action.error.message;
      });
  }
});

export const { userAdded, userUpdated } = usersSlice.actions;
export default usersSlice.reducer;

⚠️ Common Edge Cases

Important

1. Direct State Mutation ❌

🚨 The Problem

Directly mutating state breaks Redux's immutability principle and causes bugs!

❌ Wrong vs ✅ Right
// ❌ WRONG - Direct mutation (without Redux Toolkit)
function todosReducer(state = [], action) {
  switch (action.type) {
    case 'ADD_TODO':
      state.push(action.payload); // ❌ Mutates state!
      return state;
  }
}

// ✅ CORRECT - Immutable update (without Redux Toolkit)
function todosReducer(state = [], action) {
  switch (action.type) {
    case 'ADD_TODO':
      return [...state, action.payload]; // ✅ New array!
  }
}

// ✅ BEST - Redux Toolkit (Immer handles immutability)
const todosSlice = createSlice({
  name: 'todos',
  initialState: [],
  reducers: {
    addTodo(state, action) {
      state.push(action.payload); // ✅ Looks like mutation, but Immer makes it immutable!
    }
  }
});

2. Non-Serializable State ⚠️

⚠️ The Problem

Redux state should only contain plain JavaScript objects, arrays, and primitives. No functions, Promises, Dates, or class instances!

Non-Serializable Values
// ❌ WRONG - Non-serializable values
const badState = {
  user: {
    name: 'John',
    createdAt: new Date(), // ❌ Date object
    fetchData: () => {}, // ❌ Function
    promise: fetch('/api') // ❌ Promise
  }
};

// ✅ CORRECT - Serializable values
const goodState = {
  user: {
    name: 'John',
    createdAt: '2024-01-01T00:00:00Z', // ✅ ISO string
    isLoading: false, // ✅ Boolean
    data: null // ✅ Null
  }
};

3. Async Race Conditions 🏃

⚠️ The Problem

Multiple async operations can complete in unexpected order, causing stale data!

Handling Race Conditions
import { createAsyncThunk } from '@reduxjs/toolkit';

// ✅ Solution: Use createAsyncThunk with condition
export const fetchUser = createAsyncThunk(
  'users/fetchUser',
  async (userId, { getState, requestId }) => {
    const { currentRequestId, loading } = getState().users;
    
    // Cancel if there's already a request in progress
    if (loading !== 'pending' || requestId !== currentRequestId) {
      return;
    }
    
    const response = await fetch(`/api/users/${userId}`);
    return response.json();
  },
  {
    condition: (userId, { getState }) => {
      const { loading } = getState().users;
      // Don't fetch if already loading
      return loading !== 'pending';
    }
  }
);

4. Nested State Performance 🐌

💡 Solution: Normalize State

Instead of deeply nested objects, use a flat structure with IDs as keys!

Normalized State Structure
// ❌ BAD - Nested structure
const badState = {
  posts: [
    {
      id: 1,
      title: 'Post 1',
      author: {
        id: 1,
        name: 'John',
        posts: [/* circular reference! */]
      },
      comments: [
        { id: 1, text: 'Comment 1', author: {/* ... */} }
      ]
    }
  ]
};

// ✅ GOOD - Normalized structure
const goodState = {
  posts: {
    byId: {
      1: { id: 1, title: 'Post 1', authorId: 1, commentIds: [1] }
    },
    allIds: [1]
  },
  users: {
    byId: {
      1: { id: 1, name: 'John', postIds: [1] }
    },
    allIds: [1]
  },
  comments: {
    byId: {
      1: { id: 1, text: 'Comment 1', authorId: 1 }
    },
    allIds: [1]
  }
};

// Use createEntityAdapter for automatic normalization!
import { createEntityAdapter } from '@reduxjs/toolkit';

const postsAdapter = createEntityAdapter();
const initialState = postsAdapter.getInitialState();

💼 Interview Questions (Hinglish)

Must Know
1
Redux kya hai aur iska use kab karna chahiye?
▼

Answer:

Redux ek predictable state container hai JavaScript apps ke liye. Ye ek central store hai jahan app ka saara data ek jagah store hota hai.

Kab use karein:

  • Large applications: Jab app bada ho aur bahut saare components ko same data chahiye
  • Complex state: Jab state management complex ho jaye aur props drilling problem ho
  • Shared state: Jab multiple components ko same state access aur update karna ho
  • Predictability: Jab aapko chahiye ki state changes predictable aur traceable ho

Kab use NAHI karein:

  • Small apps with simple state
  • Jab sirf ek component ko state chahiye
  • Jab aap React seekh rahe ho (pehle React master karo!)

Real example: E-commerce app mein user info, cart items, product list - ye sab different components mein chahiye. Redux se ye sab ek jagah manage ho jata hai!

2
Redux ka data flow explain karo - Action, Reducer, Store kaise kaam karte hain?
▼

Answer:

Redux ka data flow ek one-way street hai:

1. Action (📤):

  • Ye ek plain JavaScript object hai jo batata hai KYA hua
  • Isme type (mandatory) aur payload (optional data) hota hai
  • Example: { type: 'ADD_TODO', payload: 'Buy milk' }

2. Dispatch (🚀):

  • Action ko store tak bhejne ka tarika
  • dispatch(addTodo('Buy milk'))
  • Ye ONLY way hai state change karne ka

3. Reducer (⚙️):

  • Pure function jo batata hai state KAISE change hoga
  • Input: (previousState, action)
  • Output: newState
  • Ye state ko directly mutate NAHI karta, new state return karta hai

4. Store (🗄️):

  • Central place jahan puri app ka state rehta hai
  • Sirf ek hi store hota hai puri app mein
  • Components store se data read kar sakte hain (useSelector)

Flow: User clicks button → Dispatch action → Reducer processes → Store updates → Components re-render

3
Redux Toolkit kya hai aur purane Redux se kaise better hai?
▼

Answer:

Redux Toolkit (RTK) Redux ka official, recommended way hai. Ye Redux ko simple aur powerful banata hai!

Purane Redux ki problems:

  • Bahut zyada boilerplate code
  • Manual immutable updates (spread operators ka maze)
  • Manually action types aur action creators banana
  • Middleware setup complex tha

Redux Toolkit ke benefits:

  • createSlice: Reducers aur actions ek saath automatically create ho jate hain
  • Immer built-in: "Mutating" logic likh sakte ho, Immer immutability handle karta hai
  • configureStore: Store setup simple, Redux DevTools aur middleware by default
  • createAsyncThunk: Async logic handle karna easy
  • Less code: 50-60% kam code likhna padta hai!

Example comparison:

Purana Redux: 50 lines of code

Redux Toolkit: 15 lines of code (same functionality!)

Interview tip: Hamesha bolo ki aap Redux Toolkit use karte ho, purana way outdated hai!

4
Reducer pure function kyu hona chahiye? Side effects kya hain?
▼

Answer:

Pure function ka matlab:

  • Same input → Always same output
  • No side effects (external state change nahi karta)
  • Predictable aur testable

Reducer pure kyu ho:

  • Predictability: Debugging easy ho jati hai
  • Time-travel debugging: Redux DevTools mein actions replay kar sakte ho
  • Testing: Test karna bahut easy
  • Performance: React optimization techniques kaam karti hain

Side effects examples (❌ Reducer mein NAHI karna):

  • API calls
  • localStorage access
  • console.log
  • Math.random() ya Date.now()
  • DOM manipulation

Side effects kahan karein (✅):

  • Middleware: Redux Thunk, Redux Saga
  • createAsyncThunk: Redux Toolkit ka built-in solution
  • Components: useEffect mein

Interview tip: Agar interviewer puchhe "API call kahan karoge?" - kabhi mat bolo "reducer mein"! Bolo "createAsyncThunk ya middleware mein"

5
State ko directly mutate kyu nahi karna chahiye? Redux Toolkit mein kaise handle hota hai?
▼

Answer:

Direct mutation ki problem:

  • React ko pata nahi chalta ki state change hua (reference same rehta hai)
  • Component re-render nahi hota
  • Redux DevTools kaam nahi karta
  • Time-travel debugging break ho jata hai
  • Bugs jo debug karna mushkil hai

Purane Redux mein solution:

  • Spread operators: { ...state, newProp: value }
  • Array methods: [...state, newItem]
  • Bahut verbose aur error-prone

Redux Toolkit ka magic (Immer):

  • Redux Toolkit internally Immer library use karta hai
  • Tum "mutating" code likh sakte ho: state.value += 1
  • Immer automatically immutable update kar deta hai
  • Code simple aur readable ho jata hai

Example:

// Purana Redux - Complex
return {
  ...state,
  todos: state.todos.map(todo =>
    todo.id === action.id
      ? { ...todo, completed: !todo.completed }
      : todo
  )
};

// Redux Toolkit - Simple!
const todo = state.todos.find(t => t.id === action.id);
todo.completed = !todo.completed;

Interview tip: Bolo "Redux Toolkit mein Immer built-in hai, isliye mutating syntax use kar sakte hain safely!"

6
useSelector aur useDispatch hooks kaise kaam karte hain?
▼

Answer:

useSelector (🎯):

  • Redux store se data read karne ke liye
  • Selector function pass karte ho jo state se specific data return karta hai
  • Jab selected data change hota hai, component re-render hota hai
  • Multiple useSelector calls kar sakte ho ek component mein

useDispatch (🚀):

  • Actions dispatch karne ke liye
  • Store ka dispatch function return karta hai
  • Isse actions trigger kar sakte ho

Performance tip:

  • useSelector by default strict equality (===) check karta hai
  • Agar new object/array return karte ho, har render pe re-render hoga
  • Solution: createSelector use karo (memoization)

Example:

import { useSelector, useDispatch } from 'react-redux';

function TodoList() {
  // Read data from store
  const todos = useSelector(state => state.todos);
  const count = useSelector(state => state.todos.length);
  
  // Get dispatch function
  const dispatch = useDispatch();
  
  // Dispatch actions
  const handleAdd = () => {
    dispatch(addTodo('New todo'));
  };
}

Interview tip: Agar performance question aaye, bolo "createSelector use karta hun memoization ke liye"

7
Async operations Redux mein kaise handle karte hain? createAsyncThunk explain karo.
▼

Answer:

Reducers pure functions hain, isliye async operations directly nahi kar sakte. Redux Toolkit mein createAsyncThunk use karte hain!

createAsyncThunk kya karta hai:

  • Automatically 3 action types create karta hai: pending, fulfilled, rejected
  • API call ya async operation handle karta hai
  • Loading states automatically manage ho jati hain
  • Error handling built-in

3 States:

  • pending: Request start hua (loading = true)
  • fulfilled: Success! Data mil gaya
  • rejected: Error hua

Complete example:

import { createAsyncThunk, createSlice } from '@reduxjs/toolkit';

// Create async thunk
export const fetchUsers = createAsyncThunk(
  'users/fetchUsers',
  async () => {
    const response = await fetch('/api/users');
    return response.json();
  }
);

const usersSlice = createSlice({
  name: 'users',
  initialState: {
    data: [],
    loading: false,
    error: null
  },
  extraReducers: (builder) => {
    builder
      .addCase(fetchUsers.pending, (state) => {
        state.loading = true;
      })
      .addCase(fetchUsers.fulfilled, (state, action) => {
        state.loading = false;
        state.data = action.payload;
      })
      .addCase(fetchUsers.rejected, (state, action) => {
        state.loading = false;
        state.error = action.error.message;
      });
  }
});

Alternative: Redux Thunk (purana way) ya Redux Saga (complex apps ke liye)

Interview tip: Bolo "createAsyncThunk Redux Toolkit ka recommended way hai async operations ke liye"

8
Middleware kya hai Redux mein? Example do.
▼

Answer:

Middleware ek interceptor hai jo action dispatch hone aur reducer tak pahunchne ke beech mein kaam karta hai.

Middleware ka use:

  • Logging: Har action aur state change log karo
  • Async operations: API calls handle karo
  • Error handling: Errors catch aur report karo
  • Analytics: User actions track karo
  • Routing: Navigation handle karo

Popular middleware:

  • Redux Thunk: Async actions (Redux Toolkit mein by default)
  • Redux Saga: Complex async flows
  • Redux Logger: Development logging

Custom middleware example:

// Logger middleware
const logger = store => next => action => {
  console.log('Dispatching:', action);
  const result = next(action);
  console.log('Next state:', store.getState());
  return result;
};

// Add to store
const store = configureStore({
  reducer: rootReducer,
  middleware: (getDefaultMiddleware) =>
    getDefaultMiddleware().concat(logger)
});

Flow: Dispatch → Middleware 1 → Middleware 2 → Reducer → Store

Interview tip: Bolo "Redux Toolkit mein Redux Thunk by default included hai, isliye async operations easily handle ho jate hain"

9
Redux DevTools kya hai aur kaise use karte hain?
▼

Answer:

Redux DevTools ek browser extension hai jo Redux debugging ko super easy banata hai!

Features:

  • Action history: Saare dispatched actions dekh sakte ho
  • State inspection: Har action ke baad state kya tha
  • Time-travel debugging: Actions ko undo/redo kar sakte ho
  • State diff: State mein kya change hua, clearly dikhta hai
  • Action replay: Actions ko replay kar sakte ho
  • Export/Import: State aur actions export kar sakte ho

Setup (Redux Toolkit mein automatic!):

import { configureStore } from '@reduxjs/toolkit';

// Redux Toolkit automatically enables DevTools!
const store = configureStore({
  reducer: rootReducer,
  // DevTools enabled by default in development
});

Kaise use karein:

  1. Chrome/Firefox mein Redux DevTools extension install karo
  2. App run karo
  3. Browser DevTools open karo (F12)
  4. "Redux" tab mein jao
  5. Actions aur state changes dekho!

Pro tips:

  • Production mein DevTools disable karo (security)
  • Time-travel debugging se bugs easily find ho jate hain
  • State diff se exactly pata chalta hai kya change hua

Interview tip: Bolo "Redux DevTools development mein bahut helpful hai, time-travel debugging se bugs quickly fix ho jate hain"

10
Redux vs Context API - kab kya use karein?
▼

Answer:

Ye bahut common interview question hai! Dono ka apna use case hai.

Context API use karo jab:

  • Simple state sharing (theme, language, user info)
  • State rarely change hota hai
  • Small to medium apps
  • No complex state logic
  • Props drilling avoid karna hai

Redux use karo jab:

  • Large, complex applications
  • State frequently change hota hai
  • Complex state logic aur transformations
  • Time-travel debugging chahiye
  • Middleware chahiye (logging, analytics)
  • Multiple developers kaam kar rahe hain
  • Predictable state updates chahiye

Performance difference:

  • Context API: Jab context value change hota hai, saare consumers re-render hote hain (even if unko wo data nahi chahiye)
  • Redux: Sirf wo components re-render hote hain jinko selected data change hua hai

Comparison table:

Feature Context API Redux
Setup Simple More setup needed
Performance Can cause unnecessary re-renders Optimized with selectors
DevTools No Yes (Redux DevTools)
Middleware No Yes
Learning curve Easy Steeper

Best practice: Dono ko saath mein bhi use kar sakte ho! Context API for simple things, Redux for complex state.

Interview tip: Bolo "Context API aur Redux competitors nahi hain, dono ka apna use case hai. Small apps mein Context API sufficient hai, large apps mein Redux better hai"

📚 Redux Definitions & Concepts

Reference Guide

1. Redux

📖 Formal Definition

Redux is a predictable state container for JavaScript applications. It helps you write applications that behave consistently, run in different environments (client, server, and native), and are easy to test. Redux centralizes your application's state and logic, enabling powerful capabilities like undo/redo, state persistence, and time-travel debugging.

👶 Simple Explanation

Redux is like a big toy box for your app's data. Instead of every room (component) having its own toys (data), there's ONE central toy box where all toys are kept. Everyone can see what's in the box, but there are rules about how to add or remove toys. This makes it easy to know where everything is and what's happening!

💡 Real-World Example

E-commerce Application: In an online shopping app, you need to manage user authentication, shopping cart items, product listings, order history, and user preferences. Without Redux, passing this data between components becomes messy (props drilling). With Redux, all this data lives in one central store, and any component can access what it needs!

2. Store

📖 Formal Definition

The Store is the central repository that holds the entire state tree of your application. There is only one store in a Redux application. The store has the following responsibilities: holds application state, allows access to state via getState(), allows state to be updated via dispatch(action), registers listeners via subscribe(listener), and handles unregistering of listeners.

👶 Simple Explanation

The Store is like a bank vault. It safely keeps all your app's important information (money/data). You can check what's inside (getState), but you can't just walk in and take things. You have to follow the bank's rules (dispatch actions) to make changes. The bank also tells you when something changes (subscribe)!

Creating a Store
import { configureStore } from '@reduxjs/toolkit';
import counterReducer from './features/counter/counterSlice';
import userReducer from './features/user/userSlice';

const store = configureStore({
  reducer: {
    counter: counterReducer,
    user: userReducer,
  },
  // Redux DevTools enabled by default
  // Middleware (Redux Thunk) included by default
});

export default store;

3. Action

📖 Formal Definition

An Action is a plain JavaScript object that describes an intention to change the state. Actions are the only way to get data into the store. Every action must have a type property (string) that indicates the type of action being performed. Actions can optionally have a payload property containing additional data needed to perform the action.

👶 Simple Explanation

An Action is like a note or message you write. It says "Hey, I want to do something!" For example, "I want to add a toy to the box" or "I want to remove this toy". The note describes WHAT you want to do, not HOW to do it. Someone else (the reducer) will read the note and do the actual work!

Action Examples
// Simple action (no payload)
{
  type: 'counter/increment'
}

// Action with payload
{
  type: 'counter/incrementByAmount',
  payload: 5
}

// Complex action
{
  type: 'todos/todoAdded',
  payload: {
    id: 1,
    text: 'Learn Redux',
    completed: false
  }
}

4. Reducer

📖 Formal Definition

A Reducer is a pure function that takes the previous state and an action, and returns the next state: (previousState, action) => newState. Reducers specify how the application's state changes in response to actions. They must be pure functions - same inputs always produce the same output, with no side effects. Reducers should never mutate the existing state; instead, they should return a new state object.

👶 Simple Explanation

A Reducer is like a recipe book with rules. When someone gives you a note (action) saying "add a toy", you look in the recipe book to see HOW to add the toy. The recipe says "take the old toy box, make a NEW toy box with all the old toys PLUS the new toy". You never change the old toy box directly - you always make a new one!

Reducer with Redux Toolkit
import { createSlice } from '@reduxjs/toolkit';

const counterSlice = createSlice({
  name: 'counter',
  initialState: {
    value: 0,
    history: []
  },
  reducers: {
    // Redux Toolkit uses Immer, so we can "mutate" state
    increment(state) {
      state.value += 1;
      state.history.push('increment');
    },
    decrement(state) {
      state.value -= 1;
      state.history.push('decrement');
    },
    incrementByAmount(state, action) {
      state.value += action.payload;
      state.history.push(`increment by ${action.payload}`);
    },
    reset(state) {
      state.value = 0;
      state.history = [];
    }
  }
});

export const { increment, decrement, incrementByAmount, reset } = counterSlice.actions;
export default counterSlice.reducer;

5. Dispatch

📖 Formal Definition

Dispatch is a function provided by the Redux store that is used to send (dispatch) actions to the store. It is the only way to trigger a state change. When you call dispatch(action), the store runs its reducer function with the current state and the dispatched action, and saves the returned new state. It then notifies all subscribers that the state has been updated.

👶 Simple Explanation

Dispatch is like pressing a doorbell or sending a letter. When you want something to happen, you "dispatch" (send) a message. It's like telling the mailman "Please deliver this letter to the toy box manager!" The dispatch function makes sure your message (action) gets to the right place (reducer).

Using Dispatch in React
import { useDispatch, useSelector } from 'react-redux';
import { increment, decrement, incrementByAmount } from './counterSlice';

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

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

6. Selector

📖 Formal Definition

A Selector is a function that extracts specific pieces of data from the Redux store state. Selectors can compute derived data, allowing Redux to store the minimal possible state. They are efficient because they can be memoized using libraries like Reselect, preventing unnecessary recalculations. Selectors help keep your components decoupled from the exact shape of the state tree.

👶 Simple Explanation

A Selector is like a magnifying glass or a search tool. Instead of looking at the entire toy box (which might be huge!), you use the selector to find exactly what you need. "Show me only the red toys" or "Count how many toys I have". It helps you find specific things quickly without looking through everything!

Using Selectors
import { useSelector } from 'react-redux';
import { createSelector } from '@reduxjs/toolkit';

// Simple selector
const selectTodos = state => state.todos;

// Memoized selector (better performance)
const selectCompletedTodos = createSelector(
  [selectTodos],
  (todos) => todos.filter(todo => todo.completed)
);

const selectTodoStats = createSelector(
  [selectTodos],
  (todos) => ({
    total: todos.length,
    completed: todos.filter(t => t.completed).length,
    active: todos.filter(t => !t.completed).length
  })
);

// Using in component
function TodoList() {
  const todos = useSelector(selectTodos);
  const completedTodos = useSelector(selectCompletedTodos);
  const stats = useSelector(selectTodoStats);
  
  // ...
}

7. Middleware

📖 Formal Definition

Middleware provides a third-party extension point between dispatching an action and the moment it reaches the reducer. Middleware can be used for logging, crash reporting, performing asynchronous tasks, routing, and more. Redux middleware is implemented as a chain of functions that wrap the store's dispatch method. Each middleware receives store's dispatch and getState functions as named arguments, and returns a function.

👶 Simple Explanation

Middleware is like a security guard or helper who stands between you and the toy box. When you send a message (action), the middleware can: 1) Read the message and write it down (logging), 2) Check if the message is allowed (validation), 3) Change the message before it goes through, 4) Do extra work like calling the internet (API calls), or 5) Stop the message completely if something's wrong!

Custom Middleware Example
// Logger middleware
const loggerMiddleware = store => next => action => {
  console.group(action.type);
  console.info('dispatching', action);
  const result = next(action);
  console.log('next state', store.getState());
  console.groupEnd();
  return result;
};

// Error handling middleware
const errorMiddleware = store => next => action => {
  try {
    return next(action);
  } catch (err) {
    console.error('Error in action:', action.type, err);
    // Send to error tracking service
    throw err;
  }
};

// Add to store
const store = configureStore({
  reducer: rootReducer,
  middleware: (getDefaultMiddleware) =>
    getDefaultMiddleware()
      .concat(loggerMiddleware)
      .concat(errorMiddleware)
});

8. Immutability

📖 Formal Definition

Immutability is the concept that data should not be changed after it's created. In Redux, state must be treated as immutable - you never modify the existing state object directly. Instead, you create a new state object with the desired changes. This is crucial for Redux's change detection mechanism, time-travel debugging, and React's rendering optimization. Redux Toolkit uses the Immer library internally to allow "mutating" syntax while maintaining immutability.

👶 Simple Explanation

Immutability means "don't change the original". Imagine you have a drawing. Instead of erasing and changing the original drawing, you make a NEW drawing with the changes. This way, you can always go back and see what the old drawing looked like! In Redux, we never change the old state - we always make a new one with our changes.

Immutability Examples
// ❌ WRONG - Mutating state directly
function badReducer(state, action) {
  state.count++; // Mutates original state!
  state.todos.push(action.payload); // Mutates array!
  return state; // Returns same reference
}

// ✅ CORRECT - Immutable updates (without Redux Toolkit)
function goodReducer(state, action) {
  return {
    ...state, // Copy all properties
    count: state.count + 1, // New value
    todos: [...state.todos, action.payload] // New array
  };
}

// ✅ BEST - Redux Toolkit with Immer
const slice = createSlice({
  name: 'example',
  initialState: { count: 0, todos: [] },
  reducers: {
    increment(state) {
      state.count++; // Looks like mutation, but Immer handles it!
    },
    addTodo(state, action) {
      state.todos.push(action.payload); // Immer makes it immutable!
    }
  }
});
↑