🚀 Redux Mastery

Complete Guide from Noob to Pro

7
Levels
100+
Topics
50+
Interview Questions
5
Projects
🟢 LEVEL 0 — Prerequisites
▼

JavaScript Basics

Definition: JavaScript is a programming language that makes websites interactive. Think of it like teaching a robot to do tasks.

🎯 Example: Variables (Boxes to store things)

// Variables are like boxes where you store things
let toyBox = "Car";  // You can change what's inside
const name = "Rajneesh";  // This cannot be changed

// Objects are like a bag with multiple pockets
const student = {
    name: "Rajneesh",
    age: 20,
    course: "Redux"
};

// Arrays are like a list
const fruits = ["Apple", "Banana", "Mango"];

// Arrow Functions (shortcut to write functions)
const greet = (name) => {
    return `Hello ${name}!`;
};

console.log(greet("Rajneesh")); // Output: Hello Rajneesh!
                                

🎯 Spread Operator (... three dots)

Definition: Spread operator allows you to copy or combine arrays/objects easily.

// Imagine you have two toy boxes
const box1 = ["Car", "Ball"];
const box2 = ["Doll", "Puzzle"];

// Spread operator combines them
const allToys = [...box1, ...box2];
console.log(allToys); // ["Car", "Ball", "Doll", "Puzzle"]

// With objects
const person = { name: "Raj", age: 20 };
const updatedPerson = { ...person, city: "Delhi" };
console.log(updatedPerson); // { name: "Raj", age: 20, city: "Delhi" }
                                

React Basics

Definition: React is a library to build user interfaces. Think of it like building with LEGO blocks - each block is a component.

🎯 Components (Building Blocks)

// Component is like a reusable template
function Button(props) {
    return ;
}

// Using the component
function App() {
    return (
        
); }

🎯 useState (Memory for Components)

Definition: useState is a hook that lets components remember things. Like a notebook where you write and update notes.

import { useState } from 'react';

function Counter() {
    // count is the value, setCount is the function to change it
    const [count, setCount] = useState(0);
    
    return (
        

Count: {count}

); }

Why State Management? (The Problem Redux Solves)

Prop Drilling Problem: Imagine passing a message through 10 people to reach the last person. That's prop drilling - passing data through many components.

❌ Problem: Prop Drilling

// Grandparent has the data
function Grandparent() {
    const user = { name: "Rajneesh" };
    return ;
}

// Parent doesn't need it, just passes it
function Parent({ user }) {
    return ;
}

// Child finally uses it
function Child({ user }) {
    return 

Hello {user.name}

; } // This is annoying! Redux solves this.

✅ Solution: Redux (Central Store)

Redux creates a central store (like a warehouse) where all components can directly get data without passing through others.

🟡 LEVEL 1 — Redux Fundamentals
▼

What is Redux?

Definition: Redux is a predictable state container for JavaScript apps. It helps you manage the state (data) of your application in a single place.

Simple Explanation: Imagine a big warehouse (Store) where all your app's data is kept. Instead of each room (component) having its own storage, everyone goes to the warehouse to get or update things.

🏪 The Warehouse Analogy

  • Store: The warehouse building
  • State: All items in the warehouse
  • Actions: Request forms (like "Give me item X")
  • Reducers: Warehouse workers who process requests
  • Dispatch: Submitting the request form

Core Principles

1️⃣ Single Source of Truth +

Definition: The entire state of your application is stored in one object tree within a single store.

Simple: All data lives in one place, not scattered everywhere.

// The entire app state in one object
const state = {
    user: { name: "Rajneesh", loggedIn: true },
    todos: ["Learn Redux", "Build Project"],
    cart: { items: [], total: 0 }
};
                                    
2️⃣ State is Read-Only +

Definition: The only way to change state is to emit an action, an object describing what happened.

Simple: You can't directly change data. You must send a request (action) to change it.

// ❌ Wrong - Direct mutation
state.user.name = "New Name";

// ✅ Correct - Dispatch an action
dispatch({ 
    type: "UPDATE_NAME", 
    payload: "New Name" 
});
                                    
3️⃣ Changes are Made with Pure Functions +

Definition: Reducers are pure functions that take the previous state and an action, and return the next state.

Pure Function: Same input always gives same output, no side effects.

// Pure function - predictable
function add(a, b) {
    return a + b;  // Always same result for same inputs
}

// Impure function - unpredictable
function addRandom(a) {
    return a + Math.random();  // Different result each time
}

// Redux reducer (pure function)
function counterReducer(state = 0, action) {
    if (action.type === "INCREMENT") {
        return state + 1;
    }
    return state;
}
                                    

Redux Data Flow (The Journey)

🔄 Step-by-Step Flow

  1. User clicks button → Triggers an event
  2. Dispatch an Action → Send request to store
  3. Reducer processes → Updates state
  4. Store updates → New state created
  5. UI re-renders → Shows new data
// 1. Define Action Types
const INCREMENT = "INCREMENT";
const DECREMENT = "DECREMENT";

// 2. Create Actions (Request forms)
const increment = () => ({ type: INCREMENT });
const decrement = () => ({ type: DECREMENT });

// 3. Create Reducer (Warehouse worker)
function counterReducer(state = 0, action) {
    switch(action.type) {
        case INCREMENT:
            return state + 1;
        case DECREMENT:
            return state - 1;
        default:
            return state;
    }
}

// 4. Create Store (Warehouse)
import { createStore } from 'redux';
const store = createStore(counterReducer);

// 5. Subscribe to changes (Get notifications)
store.subscribe(() => {
    console.log("State changed:", store.getState());
});

// 6. Dispatch actions (Submit requests)
store.dispatch(increment()); // State: 1
store.dispatch(increment()); // State: 2
store.dispatch(decrement()); // State: 1
                            

Complete Manual Redux Example

Note This is old Redux. We don't use this in real projects, but it helps understand the concept.

// ========== COMPLETE REDUX EXAMPLE ==========

// 1. ACTION TYPES (Constants)
const ADD_TODO = "ADD_TODO";
const TOGGLE_TODO = "TOGGLE_TODO";
const DELETE_TODO = "DELETE_TODO";

// 2. ACTION CREATORS (Functions that return actions)
const addTodo = (text) => ({
    type: ADD_TODO,
    payload: {
        id: Date.now(),
        text: text,
        completed: false
    }
});

const toggleTodo = (id) => ({
    type: TOGGLE_TODO,
    payload: id
});

const deleteTodo = (id) => ({
    type: DELETE_TODO,
    payload: id
});

// 3. REDUCER (Pure function that updates state)
const initialState = {
    todos: []
};

function todoReducer(state = initialState, action) {
    switch(action.type) {
        case ADD_TODO:
            return {
                ...state,
                todos: [...state.todos, action.payload]
            };
        
        case TOGGLE_TODO:
            return {
                ...state,
                todos: state.todos.map(todo =>
                    todo.id === action.payload
                        ? { ...todo, completed: !todo.completed }
                        : todo
                )
            };
        
        case DELETE_TODO:
            return {
                ...state,
                todos: state.todos.filter(todo => todo.id !== action.payload)
            };
        
        default:
            return state;
    }
}

// 4. CREATE STORE
import { createStore } from 'redux';
const store = createStore(todoReducer);

// 5. SUBSCRIBE (Listen to changes)
store.subscribe(() => {
    console.log("Current State:", store.getState());
});

// 6. DISPATCH ACTIONS (Use the store)
store.dispatch(addTodo("Learn Redux"));
store.dispatch(addTodo("Build Project"));
store.dispatch(toggleTodo(1));
store.dispatch(deleteTodo(2));

console.log("Final State:", store.getState());
                            
🟠 LEVEL 2 — Redux Toolkit (Modern Redux)
▼

Why Redux Toolkit?

Definition: Redux Toolkit (RTK) is the official, recommended way to write Redux logic. It simplifies Redux code and includes best practices by default.

🎯 Problems RTK Solves

  • Too much boilerplate: Old Redux needed lots of repetitive code
  • Complex setup: Setting up store was complicated
  • Immutability: Hard to update state without mutations
  • Async logic: Handling API calls was tricky

✨ RTK Benefits

  • Less code: Write 50% less code
  • Built-in Immer: Write "mutating" code that's actually immutable
  • Built-in Thunks: Easy async operations
  • DevTools: Automatic Redux DevTools setup

RTK Main APIs

1️⃣ configureStore() +

Definition: Creates the Redux store with good defaults. Automatically sets up Redux DevTools and middleware.

import { configureStore } from '@reduxjs/toolkit';
import counterReducer from './counterSlice';
import userReducer from './userSlice';

// Create store - super simple!
const store = configureStore({
    reducer: {
        counter: counterReducer,
        user: userReducer
    }
});

export default store;

// Old Redux way (much more code):
// import { createStore, combineReducers, applyMiddleware } from 'redux';
// import thunk from 'redux-thunk';
// import { composeWithDevTools } from 'redux-devtools-extension';
// const rootReducer = combineReducers({ counter, user });
// const store = createStore(rootReducer, composeWithDevTools(applyMiddleware(thunk)));
                                    
2️⃣ createSlice() +

Definition: A function that accepts an initial state, an object of reducer functions, and a "slice name", and automatically generates action creators and action types.

Simple: One function to create actions and reducers together!

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

// Create a slice (piece of state)
const counterSlice = createSlice({
    name: 'counter',  // Name of this slice
    initialState: {
        value: 0
    },
    reducers: {
        // Actions are automatically created!
        increment: (state) => {
            // Looks like mutation, but Immer makes it immutable
            state.value += 1;
        },
        decrement: (state) => {
            state.value -= 1;
        },
        incrementByAmount: (state, action) => {
            state.value += action.payload;
        },
        reset: (state) => {
            state.value = 0;
        }
    }
});

// Export actions (automatically created)
export const { increment, decrement, incrementByAmount, reset } = counterSlice.actions;

// Export reducer
export default counterSlice.reducer;

// Usage in component:
// import { increment } from './counterSlice';
// dispatch(increment());
                                    
3️⃣ createAsyncThunk() +

Definition: A function that accepts a Redux action type string and a callback function that returns a promise. It generates promise lifecycle action types based on the action type prefix.

Simple: Easy way to handle API calls in Redux!

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

// Create async thunk for API call
export const fetchUsers = createAsyncThunk(
    'users/fetchUsers',  // Action type prefix
    async () => {
        const response = await axios.get('https://api.example.com/users');
        return response.data;  // This becomes action.payload
    }
);

const usersSlice = createSlice({
    name: 'users',
    initialState: {
        users: [],
        loading: false,
        error: null
    },
    reducers: {},
    extraReducers: (builder) => {
        builder
            // When API call starts
            .addCase(fetchUsers.pending, (state) => {
                state.loading = true;
                state.error = null;
            })
            // When API call succeeds
            .addCase(fetchUsers.fulfilled, (state, action) => {
                state.loading = false;
                state.users = action.payload;
            })
            // When API call fails
            .addCase(fetchUsers.rejected, (state, action) => {
                state.loading = false;
                state.error = action.error.message;
            });
    }
});

export default usersSlice.reducer;

// Usage in component:
// dispatch(fetchUsers());
                                    

Complete RTK Example - Todo App

📁 Project Structure:
  • src/
    • app/
      • store.js
    • features/
      • todos/
        • todosSlice.js
        • TodoList.jsx
    • App.jsx
    • main.jsx
// ========== store.js ==========
import { configureStore } from '@reduxjs/toolkit';
import todosReducer from '../features/todos/todosSlice';

export const store = configureStore({
    reducer: {
        todos: todosReducer
    }
});
                            
// ========== todosSlice.js ==========
import { createSlice } from '@reduxjs/toolkit';

const todosSlice = createSlice({
    name: 'todos',
    initialState: {
        items: [],
        filter: 'all' // all, active, completed
    },
    reducers: {
        addTodo: (state, action) => {
            state.items.push({
                id: Date.now(),
                text: action.payload,
                completed: false
            });
        },
        toggleTodo: (state, action) => {
            const todo = state.items.find(t => t.id === action.payload);
            if (todo) {
                todo.completed = !todo.completed;
            }
        },
        deleteTodo: (state, action) => {
            state.items = state.items.filter(t => t.id !== action.payload);
        },
        editTodo: (state, action) => {
            const todo = state.items.find(t => t.id === action.payload.id);
            if (todo) {
                todo.text = action.payload.text;
            }
        },
        setFilter: (state, action) => {
            state.filter = action.payload;
        },
        clearCompleted: (state) => {
            state.items = state.items.filter(t => !t.completed);
        }
    }
});

export const { 
    addTodo, 
    toggleTodo, 
    deleteTodo, 
    editTodo, 
    setFilter, 
    clearCompleted 
} = todosSlice.actions;

// Selectors
export const selectAllTodos = (state) => state.todos.items;
export const selectFilter = (state) => state.todos.filter;
export const selectFilteredTodos = (state) => {
    const { items, filter } = state.todos;
    if (filter === 'active') return items.filter(t => !t.completed);
    if (filter === 'completed') return items.filter(t => t.completed);
    return items;
};

export default todosSlice.reducer;
                            
// ========== TodoList.jsx ==========
import React, { useState } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import {
    addTodo,
    toggleTodo,
    deleteTodo,
    setFilter,
    clearCompleted,
    selectFilteredTodos,
    selectFilter
} from './todosSlice';

function TodoList() {
    const [input, setInput] = useState('');
    const todos = useSelector(selectFilteredTodos);
    const filter = useSelector(selectFilter);
    const dispatch = useDispatch();

    const handleSubmit = (e) => {
        e.preventDefault();
        if (input.trim()) {
            dispatch(addTodo(input));
            setInput('');
        }
    };

    return (
        

Redux Todo App

setInput(e.target.value)} placeholder="What needs to be done?" />
    {todos.map(todo => (
  • dispatch(toggleTodo(todo.id))} /> {todo.text}
  • ))}
); } export default TodoList;
// ========== main.jsx ==========
import React from 'react';
import ReactDOM from 'react-dom/client';
import { Provider } from 'react-redux';
import { store } from './app/store';
import App from './App';

ReactDOM.createRoot(document.getElementById('root')).render(
    
        
            
        
    
);
                            
🟣 LEVEL 3 — Async Redux (API Calls)
▼

Understanding Async Operations

Definition: Asynchronous operations are tasks that take time to complete, like fetching data from an API. Redux Toolkit provides createAsyncThunk to handle these operations.

🎯 Three States of Async Operations

  • Pending: Request is in progress (show loading spinner)
  • Fulfilled: Request succeeded (show data)
  • Rejected: Request failed (show error message)

Complete Async Example - User Management

// ========== usersSlice.js ==========
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';
import axios from 'axios';

const API_URL = 'https://jsonplaceholder.typicode.com/users';

// Async thunk to fetch users
export const fetchUsers = createAsyncThunk(
    'users/fetchUsers',
    async (_, { rejectWithValue }) => {
        try {
            const response = await axios.get(API_URL);
            return response.data;
        } catch (error) {
            return rejectWithValue(error.response.data);
        }
    }
);

// Async thunk to add user
export const addUser = createAsyncThunk(
    'users/addUser',
    async (userData, { rejectWithValue }) => {
        try {
            const response = await axios.post(API_URL, userData);
            return response.data;
        } catch (error) {
            return rejectWithValue(error.response.data);
        }
    }
);

// Async thunk to update user
export const updateUser = createAsyncThunk(
    'users/updateUser',
    async ({ id, userData }, { rejectWithValue }) => {
        try {
            const response = await axios.put(`${API_URL}/${id}`, userData);
            return response.data;
        } catch (error) {
            return rejectWithValue(error.response.data);
        }
    }
);

// Async thunk to delete user
export const deleteUser = createAsyncThunk(
    'users/deleteUser',
    async (id, { rejectWithValue }) => {
        try {
            await axios.delete(`${API_URL}/${id}`);
            return id;
        } catch (error) {
            return rejectWithValue(error.response.data);
        }
    }
);

const usersSlice = createSlice({
    name: 'users',
    initialState: {
        users: [],
        loading: false,
        error: null,
        success: false
    },
    reducers: {
        clearError: (state) => {
            state.error = null;
        },
        clearSuccess: (state) => {
            state.success = false;
        }
    },
    extraReducers: (builder) => {
        builder
            // Fetch Users
            .addCase(fetchUsers.pending, (state) => {
                state.loading = true;
                state.error = null;
            })
            .addCase(fetchUsers.fulfilled, (state, action) => {
                state.loading = false;
                state.users = action.payload;
            })
            .addCase(fetchUsers.rejected, (state, action) => {
                state.loading = false;
                state.error = action.payload || 'Failed to fetch users';
            })
            
            // Add User
            .addCase(addUser.pending, (state) => {
                state.loading = true;
            })
            .addCase(addUser.fulfilled, (state, action) => {
                state.loading = false;
                state.users.push(action.payload);
                state.success = true;
            })
            .addCase(addUser.rejected, (state, action) => {
                state.loading = false;
                state.error = action.payload || 'Failed to add user';
            })
            
            // Update User
            .addCase(updateUser.fulfilled, (state, action) => {
                const index = state.users.findIndex(u => u.id === action.payload.id);
                if (index !== -1) {
                    state.users[index] = action.payload;
                }
                state.success = true;
            })
            
            // Delete User
            .addCase(deleteUser.fulfilled, (state, action) => {
                state.users = state.users.filter(u => u.id !== action.payload);
                state.success = true;
            });
    }
});

export const { clearError, clearSuccess } = usersSlice.actions;
export default usersSlice.reducer;
                            
// ========== UserList.jsx ==========
import React, { useEffect, useState } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import {
    fetchUsers,
    addUser,
    updateUser,
    deleteUser,
    clearError,
    clearSuccess
} from './usersSlice';

function UserList() {
    const dispatch = useDispatch();
    const { users, loading, error, success } = useSelector(state => state.users);
    
    const [formData, setFormData] = useState({
        name: '',
        email: '',
        phone: ''
    });
    const [editingId, setEditingId] = useState(null);

    useEffect(() => {
        dispatch(fetchUsers());
    }, [dispatch]);

    useEffect(() => {
        if (success) {
            alert('Operation successful!');
            dispatch(clearSuccess());
            setFormData({ name: '', email: '', phone: '' });
            setEditingId(null);
        }
    }, [success, dispatch]);

    const handleSubmit = (e) => {
        e.preventDefault();
        if (editingId) {
            dispatch(updateUser({ id: editingId, userData: formData }));
        } else {
            dispatch(addUser(formData));
        }
    };

    const handleEdit = (user) => {
        setFormData({
            name: user.name,
            email: user.email,
            phone: user.phone
        });
        setEditingId(user.id);
    };

    const handleDelete = (id) => {
        if (window.confirm('Are you sure?')) {
            dispatch(deleteUser(id));
        }
    };

    if (loading) {
        return 
Loading...
; } return (

User Management

{error && (
{error}
)}
setFormData({...formData, name: e.target.value})} required /> setFormData({...formData, email: e.target.value})} required /> setFormData({...formData, phone: e.target.value})} required /> {editingId && ( )}
{users.map(user => (

{user.name}

📧 {user.email}

📱 {user.phone}

))}
); } export default UserList;

Error Handling Best Practices

// ========== Advanced Error Handling ==========

// 1. Create a custom error handler
export const handleAsyncError = (error) => {
    if (error.response) {
        // Server responded with error
        return {
            message: error.response.data.message || 'Server error',
            status: error.response.status
        };
    } else if (error.request) {
        // Request made but no response
        return {
            message: 'No response from server',
            status: 0
        };
    } else {
        // Something else happened
        return {
            message: error.message || 'Unknown error',
            status: -1
        };
    }
};

// 2. Use in async thunk
export const fetchData = createAsyncThunk(
    'data/fetch',
    async (_, { rejectWithValue }) => {
        try {
            const response = await axios.get('/api/data');
            return response.data;
        } catch (error) {
            const errorData = handleAsyncError(error);
            return rejectWithValue(errorData);
        }
    }
);

// 3. Global error toast component
function ErrorToast() {
    const error = useSelector(state => state.app.error);
    const dispatch = useDispatch();

    useEffect(() => {
        if (error) {
            const timer = setTimeout(() => {
                dispatch(clearError());
            }, 5000);
            return () => clearTimeout(timer);
        }
    }, [error, dispatch]);

    if (!error) return null;

    return (
        
{error.message}
); }
🔵 LEVEL 4 — Advanced Patterns
▼

1. Normalized State

Definition: Normalized state means storing data in a flat structure with IDs as keys, instead of nested arrays and objects. This makes updates faster and easier.

❌ Non-Normalized (Bad)

const state = {
    posts: [
        {
            id: 1,
            title: "Post 1",
            author: { id: 1, name: "Rajneesh" },
            comments: [
                { id: 1, text: "Great!" },
                { id: 2, text: "Nice!" }
            ]
        },
        {
            id: 2,
            title: "Post 2",
            author: { id: 1, name: "Rajneesh" },  // Duplicate!
            comments: []
        }
    ]
};

// Problem: To update author name, you need to update multiple places!
                                

✅ Normalized (Good)

const state = {
    posts: {
        ids: [1, 2],
        entities: {
            1: { id: 1, title: "Post 1", authorId: 1, commentIds: [1, 2] },
            2: { id: 2, title: "Post 2", authorId: 1, commentIds: [] }
        }
    },
    authors: {
        ids: [1],
        entities: {
            1: { id: 1, name: "Rajneesh" }
        }
    },
    comments: {
        ids: [1, 2],
        entities: {
            1: { id: 1, text: "Great!" },
            2: { id: 2, text: "Nice!" }
        }
    }
};

// Benefit: Update author once, reflects everywhere!
                                

2. createEntityAdapter

Definition: A function that generates a set of prebuilt reducers and selectors for performing CRUD operations on normalized state.
// ========== postsSlice.js with Entity Adapter ==========
import { createSlice, createEntityAdapter, createAsyncThunk } from '@reduxjs/toolkit';
import axios from 'axios';

// Create entity adapter
const postsAdapter = createEntityAdapter({
    // Assume IDs are stored in 'id' field (default)
    selectId: (post) => post.id,
    // Sort by title
    sortComparer: (a, b) => a.title.localeCompare(b.title)
});

// Get initial state from adapter
const initialState = postsAdapter.getInitialState({
    loading: false,
    error: null
});

// Async thunks
export const fetchPosts = createAsyncThunk('posts/fetch', async () => {
    const response = await axios.get('/api/posts');
    return response.data;
});

export const addPost = createAsyncThunk('posts/add', async (post) => {
    const response = await axios.post('/api/posts', post);
    return response.data;
});

const postsSlice = createSlice({
    name: 'posts',
    initialState,
    reducers: {
        // Use adapter methods
        postAdded: postsAdapter.addOne,
        postUpdated: postsAdapter.updateOne,
        postRemoved: postsAdapter.removeOne,
        postsReceived: postsAdapter.setAll
    },
    extraReducers: (builder) => {
        builder
            .addCase(fetchPosts.pending, (state) => {
                state.loading = true;
            })
            .addCase(fetchPosts.fulfilled, (state, action) => {
                state.loading = false;
                postsAdapter.setAll(state, action.payload);
            })
            .addCase(addPost.fulfilled, (state, action) => {
                postsAdapter.addOne(state, action.payload);
            });
    }
});

// Export adapter selectors
export const {
    selectAll: selectAllPosts,
    selectById: selectPostById,
    selectIds: selectPostIds
} = postsAdapter.getSelectors((state) => state.posts);

export const { postAdded, postUpdated, postRemoved } = postsSlice.actions;
export default postsSlice.reducer;

// Usage in component:
// const posts = useSelector(selectAllPosts);
// const post = useSelector(state => selectPostById(state, postId));
                            

3. Memoized Selectors (createSelector)

Definition: Selectors that cache their results and only recalculate when inputs change. This improves performance by preventing unnecessary re-renders.
// ========== Memoized Selectors ==========
import { createSelector } from '@reduxjs/toolkit';

// Basic selectors (input selectors)
const selectTodos = (state) => state.todos.items;
const selectFilter = (state) => state.todos.filter;
const selectSearchTerm = (state) => state.todos.searchTerm;

// Memoized selector - only recalculates when todos or filter changes
export const selectFilteredTodos = createSelector(
    [selectTodos, selectFilter],
    (todos, filter) => {
        console.log('Calculating filtered todos...'); // Only logs when recalculating
        
        switch(filter) {
            case 'active':
                return todos.filter(t => !t.completed);
            case 'completed':
                return todos.filter(t => t.completed);
            default:
                return todos;
        }
    }
);

// Complex memoized selector with multiple inputs
export const selectSearchedAndFilteredTodos = createSelector(
    [selectFilteredTodos, selectSearchTerm],
    (filteredTodos, searchTerm) => {
        if (!searchTerm) return filteredTodos;
        
        return filteredTodos.filter(todo =>
            todo.text.toLowerCase().includes(searchTerm.toLowerCase())
        );
    }
);

// Selector with parameters
export const makeSelectTodosByUser = () => {
    return createSelector(
        [selectTodos, (state, userId) => userId],
        (todos, userId) => todos.filter(t => t.userId === userId)
    );
};

// Usage in component:
function TodoList() {
    const filteredTodos = useSelector(selectFilteredTodos);
    // Component only re-renders when filteredTodos actually changes
    
    return (
        
    {filteredTodos.map(todo => (
  • {todo.text}
  • ))}
); }

4. Custom Middleware

Definition: Middleware is code that runs between dispatching an action and the action reaching the reducer. It's used for logging, crash reporting, async operations, etc.
// ========== Custom Middleware Examples ==========

// 1. Logger Middleware
const loggerMiddleware = (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;
};

// 2. Error Handler Middleware
const errorMiddleware = (store) => (next) => (action) => {
    try {
        return next(action);
    } catch (error) {
        console.error('Error in action:', action.type, error);
        // Send to error tracking service
        // Sentry.captureException(error);
        throw error;
    }
};

// 3. API Middleware
const apiMiddleware = (store) => (next) => (action) => {
    if (action.type !== 'API_REQUEST') {
        return next(action);
    }
    
    const { url, method, data, onSuccess, onError } = action.payload;
    
    fetch(url, { method, body: JSON.stringify(data) })
        .then(res => res.json())
        .then(data => store.dispatch({ type: onSuccess, payload: data }))
        .catch(error => store.dispatch({ type: onError, payload: error }));
};

// 4. Local Storage Middleware (Persistence)
const localStorageMiddleware = (store) => (next) => (action) => {
    const result = next(action);
    
    // Save specific slices to localStorage
    const state = store.getState();
    localStorage.setItem('reduxState', JSON.stringify({
        user: state.user,
        cart: state.cart
    }));
    
    return result;
};

// Add middleware to store
import { configureStore } from '@reduxjs/toolkit';

const store = configureStore({
    reducer: rootReducer,
    middleware: (getDefaultMiddleware) =>
        getDefaultMiddleware().concat(
            loggerMiddleware,
            errorMiddleware,
            apiMiddleware,
            localStorageMiddleware
        )
});
                            
🟤 LEVEL 5 — Architecture & Folder Structure
▼

Feature-Based Structure (Recommended)

📁 Complete Project Structure:
my-redux-app/
├── public/
│   └── index.html
├── src/
│   ├── app/
│   │   ├── store.js                 # Redux store configuration
│   │   └── hooks.js                 # Custom hooks (useAppDispatch, useAppSelector)
│   ├── features/
│   │   ├── auth/
│   │   │   ├── authSlice.js         # Auth state logic
│   │   │   ├── authAPI.js           # Auth API calls
│   │   │   ├── Login.jsx            # Login component
│   │   │   ├── Register.jsx         # Register component
│   │   │   └── ProtectedRoute.jsx   # Protected route wrapper
│   │   ├── todos/
│   │   │   ├── todosSlice.js
│   │   │   ├── todosAPI.js
│   │   │   ├── TodoList.jsx
│   │   │   ├── TodoItem.jsx
│   │   │   └── AddTodo.jsx
│   │   ├── users/
│   │   │   ├── usersSlice.js
│   │   │   ├── usersAPI.js
│   │   │   ├── UserList.jsx
│   │   │   └── UserProfile.jsx
│   │   └── cart/
│   │       ├── cartSlice.js
│   │       ├── Cart.jsx
│   │       └── CartItem.jsx
│   ├── components/
│   │   ├── common/
│   │   │   ├── Button.jsx
│   │   │   ├── Input.jsx
│   │   │   ├── Modal.jsx
│   │   │   └── Loader.jsx
│   │   └── layout/
│   │       ├── Header.jsx
│   │       ├── Footer.jsx
│   │       └── Sidebar.jsx
│   ├── utils/
│   │   ├── api.js                   # Axios instance
│   │   ├── constants.js             # App constants
│   │   └── helpers.js               # Helper functions
│   ├── styles/
│   │   ├── global.css
│   │   └── variables.css
│   ├── App.jsx
│   ├── main.jsx
│   └── index.css
├── package.json
└── vite.config.js
// ========== app/store.js ==========
import { configureStore } from '@reduxjs/toolkit';
import authReducer from '../features/auth/authSlice';
import todosReducer from '../features/todos/todosSlice';
import usersReducer from '../features/users/usersSlice';
import cartReducer from '../features/cart/cartSlice';

export const store = configureStore({
    reducer: {
        auth: authReducer,
        todos: todosReducer,
        users: usersReducer,
        cart: cartReducer
    },
    middleware: (getDefaultMiddleware) =>
        getDefaultMiddleware({
            serializableCheck: false
        })
});

export type RootState = ReturnType;
export type AppDispatch = typeof store.dispatch;
                            
// ========== app/hooks.js ==========
import { useDispatch, useSelector } from 'react-redux';

// Custom typed hooks
export const useAppDispatch = () => useDispatch();
export const useAppSelector = useSelector;

// Custom hook for async actions with loading state
export const useAsyncAction = () => {
    const dispatch = useAppDispatch();
    const [loading, setLoading] = useState(false);
    const [error, setError] = useState(null);

    const execute = async (asyncAction) => {
        setLoading(true);
        setError(null);
        try {
            await dispatch(asyncAction).unwrap();
        } catch (err) {
            setError(err.message);
        } finally {
            setLoading(false);
        }
    };

    return { execute, loading, error };
};
                            

Ducks Pattern (Alternative)

Definition: Ducks pattern keeps all Redux logic for a feature in a single file.

// ========== features/todos/todosDuck.js ==========
// All in one file: actions, reducers, selectors, thunks

import { createSlice, createAsyncThunk, createSelector } from '@reduxjs/toolkit';
import * as api from './todosAPI';

// ===== ASYNC THUNKS =====
export const fetchTodos = createAsyncThunk('todos/fetch', api.fetchTodos);
export const addTodo = createAsyncThunk('todos/add', api.addTodo);
export const updateTodo = createAsyncThunk('todos/update', api.updateTodo);
export const deleteTodo = createAsyncThunk('todos/delete', api.deleteTodo);

// ===== SLICE =====
const todosSlice = createSlice({
    name: 'todos',
    initialState: {
        items: [],
        loading: false,
        error: null,
        filter: 'all'
    },
    reducers: {
        setFilter: (state, action) => {
            state.filter = action.payload;
        },
        clearError: (state) => {
            state.error = null;
        }
    },
    extraReducers: (builder) => {
        builder
            .addCase(fetchTodos.pending, (state) => {
                state.loading = true;
            })
            .addCase(fetchTodos.fulfilled, (state, action) => {
                state.loading = false;
                state.items = action.payload;
            })
            .addCase(fetchTodos.rejected, (state, action) => {
                state.loading = false;
                state.error = action.error.message;
            });
    }
});

// ===== ACTIONS =====
export const { setFilter, clearError } = todosSlice.actions;

// ===== SELECTORS =====
const selectTodosState = (state) => state.todos;
export const selectAllTodos = (state) => state.todos.items;
export const selectTodosLoading = (state) => state.todos.loading;
export const selectTodosError = (state) => state.todos.error;
export const selectFilter = (state) => state.todos.filter;

export const selectFilteredTodos = createSelector(
    [selectAllTodos, selectFilter],
    (todos, filter) => {
        switch(filter) {
            case 'active': return todos.filter(t => !t.completed);
            case 'completed': return todos.filter(t => t.completed);
            default: return todos;
        }
    }
);

// ===== REDUCER =====
export default todosSlice.reducer;
                            
💼 Redux Interview Questions
Q1. Redux kya hai aur kyu use karte hain? +

Hinglish: Redux ek state management library hai jo application ka saara data ek jagah store karta hai. Jab bahut saare components ko same data chahiye hota hai, tab Redux use karte hain taaki prop drilling se bach sakein.

English Definition: Redux is a predictable state container for JavaScript applications. It helps manage the global state of an application by storing all state in a single centralized store, making state management more predictable and easier to debug.

Key Points:

  • Single source of truth
  • State is read-only
  • Changes made with pure functions
  • Predictable state updates
Q2. Redux ke main components kya hain? +

Hinglish: Redux ke 4 main parts hain - Store (jahan data rakha jata hai), Actions (kya karna hai ye batata hai), Reducers (data ko update karta hai), aur Dispatch (action ko bhejta hai).

English Definition:
  • Store: The centralized state container that holds the entire application state
  • Actions: Plain JavaScript objects that describe what happened
  • Reducers: Pure functions that specify how state changes in response to actions
  • Dispatch: Method to send actions to the store
Q3. Redux Toolkit kyu better hai old Redux se? +

Hinglish: RTK mein kam code likhna padta hai, setup easy hai, aur built-in features jaise Immer aur Thunk already included hain. Old Redux mein bahut zyada boilerplate code likhna padta tha.

English Definition: Redux Toolkit (RTK) is the official, opinionated, batteries-included toolset for efficient Redux development. It includes:
  • configureStore() - Simplified store setup
  • createSlice() - Reduces boilerplate
  • createAsyncThunk() - Easy async logic
  • Built-in Immer - Immutable updates made easy
  • Redux DevTools - Automatic integration
Q4. useSelector aur useDispatch hooks ka kya use hai? +

Hinglish: useSelector store se data read karne ke liye use hota hai, aur useDispatch actions bhejne ke liye use hota hai. Ye dono React-Redux ke hooks hain.

English Definition:
  • useSelector: A hook that allows you to extract data from the Redux store state using a selector function
  • useDispatch: A hook that returns a reference to the dispatch function from the Redux store
import { useSelector, useDispatch } from 'react-redux';
import { increment } from './counterSlice';

function Counter() {
    // Read data from store
    const count = useSelector(state => state.counter.value);
    
    // Get dispatch function
    const dispatch = useDispatch();
    
    return (
        

Count: {count}

); }
Q5. Reducer pure function kyu hona chahiye? +

Hinglish: Pure function isliye chahiye kyunki same input pe hamesha same output milna chahiye. Isse Redux predictable rehta hai aur debugging easy ho jati hai. Side effects nahi hone chahiye reducer mein.

English Definition: A pure function is a function that:
  • Always returns the same output for the same input
  • Has no side effects (no API calls, no mutations, no random values)
  • Doesn't modify its arguments
This makes Redux predictable, testable, and enables time-travel debugging.
// ✅ Pure Function (Good)
function counterReducer(state = 0, action) {
    if (action.type === 'INCREMENT') {
        return state + 1;  // Returns new value
    }
    return state;
}

// ❌ Impure Function (Bad)
function badReducer(state = 0, action) {
    if (action.type === 'INCREMENT') {
        state++;  // Mutates state directly
        fetch('/api/log');  // Side effect
        return state + Math.random();  // Non-deterministic
    }
    return state;
}
                                
Q6. createSlice kya karta hai? +

Hinglish: createSlice ek function hai jo automatically actions aur reducer dono bana deta hai. Isme hum sirf reducers define karte hain aur actions automatically generate ho jate hain.

English Definition: createSlice is a function that accepts an initial state, an object of reducer functions, and a slice name. It automatically generates action creators and action types that correspond to the reducers and state.
Q7. Middleware kya hota hai Redux mein? +

Hinglish: Middleware ek layer hai jo action dispatch hone aur reducer tak pahunchne ke beech mein kaam karta hai. Iska use logging, async operations, ya error handling ke liye hota hai.

English Definition: Middleware provides a third-party extension point between dispatching an action and the moment it reaches the reducer. It's used for logging, crash reporting, performing asynchronous tasks, etc.
Q8. createAsyncThunk ka use kab karte hain? +

Hinglish: Jab API call karni ho ya koi async operation karna ho tab createAsyncThunk use karte hain. Ye automatically pending, fulfilled, aur rejected states handle kar leta hai.

English Definition: createAsyncThunk is used to handle asynchronous logic in Redux. It accepts an action type string and a callback function that returns a promise, and automatically dispatches pending/fulfilled/rejected actions based on the promise lifecycle.

🛠️ Real World Projects

1. Todo App with CRUD

Easy

Features: Add, Edit, Delete, Toggle, Filter todos with Redux Toolkit

📁 Project Structure:
todo-app/
├── src/
│   ├── app/
│   │   └── store.js
│   ├── features/
│   │   └── todos/
│   │       ├── todosSlice.js
│   │       └── TodoList.jsx
│   ├── App.jsx
│   └── main.jsx
└── package.json

2. E-commerce Shopping Cart

Medium

Features: Add to cart, Remove, Update quantity, Calculate total, Apply discounts

📝 Redux Toolkit Cheat Sheet

Quick Reference

// ========== REDUX TOOLKIT CHEAT SHEET ==========

// 1. INSTALL
npm install @reduxjs/toolkit react-redux

// 2. CREATE STORE
import { configureStore } from '@reduxjs/toolkit';
const store = configureStore({
    reducer: { counter: counterReducer }
});

// 3. CREATE SLICE
import { createSlice } from '@reduxjs/toolkit';
const slice = createSlice({
    name: 'counter',
    initialState: { value: 0 },
    reducers: {
        increment: state => { state.value += 1; }
    }
});

// 4. ASYNC THUNK
import { createAsyncThunk } from '@reduxjs/toolkit';
const fetchData = createAsyncThunk('data/fetch', async () => {
    const response = await fetch('/api/data');
    return response.json();
});

// 5. USE IN COMPONENT
import { useSelector, useDispatch } from 'react-redux';
const value = useSelector(state => state.counter.value);
const dispatch = useDispatch();
dispatch(increment());

// 6. SELECTORS
import { createSelector } from '@reduxjs/toolkit';
const selectFiltered = createSelector(
    [selectItems, selectFilter],
    (items, filter) => items.filter(i => i.type === filter)
);