{"id":23932419,"url":"https://github.com/skyybbanerjee/advance_redux","last_synced_at":"2025-02-24T02:21:05.193Z","repository":{"id":268605247,"uuid":"904906076","full_name":"skyybbanerjee/advance_redux","owner":"skyybbanerjee","description":"Some advance concepts about Redux, RTK, Async. data and side-effects handling. 👨🏻‍💻⏳","archived":false,"fork":false,"pushed_at":"2024-12-17T19:34:46.000Z","size":212,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-01-06T00:19:47.368Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/skyybbanerjee.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-12-17T19:23:00.000Z","updated_at":"2024-12-17T19:34:50.000Z","dependencies_parsed_at":"2024-12-17T20:29:40.672Z","dependency_job_id":"34962ecb-d63e-4272-bb76-686c0715a835","html_url":"https://github.com/skyybbanerjee/advance_redux","commit_stats":null,"previous_names":["skyy-banerjee/advance_redux","skyybbanerjee/advance_redux"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/skyybbanerjee%2Fadvance_redux","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/skyybbanerjee%2Fadvance_redux/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/skyybbanerjee%2Fadvance_redux/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/skyybbanerjee%2Fadvance_redux/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/skyybbanerjee","download_url":"https://codeload.github.com/skyybbanerjee/advance_redux/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":240404867,"owners_count":19796080,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-01-06T00:19:57.391Z","updated_at":"2025-02-24T02:21:05.157Z","avatar_url":"https://github.com/skyybbanerjee.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"In **Redux Toolkit (RTK)**, **action creators** are functions that automatically generate action objects for you based on the reducers you define. They simplify dispatching actions by removing the need to manually create action objects with `type` and `payload`.\n\n### Basics:\nWhen you use `createSlice` in RTK, it automatically generates **action creators** for each reducer function inside the slice. These action creators can be called directly in your components or other parts of the app.\n\n---\n\n### **What is an Action Creator?**\nAn **action creator** is a function that returns an action object. For example:\n```js\nconst addTodo = (payload) =\u003e {\n  return {\n    type: \"ADD_TODO\",\n    payload,\n  };\n};\n```\n\nIn RTK, you don't need to manually write such functions because **`createSlice` does it for you**.\n\n---\n\n### Example of Action Creators in RTK:\n#### **Step 1: Create a Slice**\n```js\nimport { createSlice } from \"@reduxjs/toolkit\";\n\nconst todoSlice = createSlice({\n  name: \"todo\",\n  initialState: { items: [] },\n  reducers: {\n    addTodo(state, action) {\n      state.items.push({ id: Date.now(), text: action.payload });\n    },\n    removeTodo(state, action) {\n      state.items = state.items.filter((item) =\u003e item.id !== action.payload);\n    },\n  },\n});\n\nexport const todoActions = todoSlice.actions; // Generated action creators\nexport default todoSlice.reducer;\n```\n\nHere:\n- RTK automatically creates **action creators**:  \n  - `todoActions.addTodo`  \n  - `todoActions.removeTodo`\n\n---\n\n#### **Step 2: Use Action Creators in a Component**\nYou can call these action creators directly and dispatch them using `useDispatch`:\n```js\nimport { useDispatch } from \"react-redux\";\nimport { todoActions } from \"./todoSlice\";\n\nfunction TodoApp() {\n  const dispatch = useDispatch();\n\n  function addHandler() {\n    dispatch(todoActions.addTodo(\"Learn Redux Toolkit\"));\n  }\n\n  function removeHandler(id) {\n    dispatch(todoActions.removeTodo(id));\n  }\n\n  return (\n    \u003cdiv\u003e\n      \u003cbutton onClick={addHandler}\u003eAdd Todo\u003c/button\u003e\n    \u003c/div\u003e\n  );\n}\n```\n\nHere:\n- `todoActions.addTodo(\"Learn Redux Toolkit\")` automatically generates the following action object:\n  ```js\n  {\n    type: \"todo/addTodo\",\n    payload: \"Learn Redux Toolkit\"\n  }\n  ```\n\n- Similarly, `todoActions.removeTodo(id)` generates an action with the appropriate type and payload.\n\n---\n\n### Key Benefits of Action Creators in RTK:\n1. **Automatic Generation**: You don't need to write action creators manually. RTK handles it.\n2. **Consistency**: Actions follow a consistent structure.\n3. **Readability**: Your code becomes cleaner and easier to read.\n4. **Error Reduction**: Avoids typos in `type` strings by using generated functions.\n\n---\n\n### Thunks as Action Creators (for Async Logic)\nIn RTK, you can also write **thunks** as action creators for async tasks:\n```js\nimport { createAsyncThunk } from \"@reduxjs/toolkit\";\n\nexport const fetchTodos = createAsyncThunk(\"todo/fetchTodos\", async () =\u003e {\n  const response = await fetch(\"https://api.example.com/todos\");\n  const data = await response.json();\n  return data; // This becomes the payload\n});\n```\nHere, `fetchTodos` is an action creator that can be dispatched.\n\n---\n\n### Conclusion:\nIn RTK:\n- **Action Creators** are automatically generated when you use `createSlice`.\n- They save time, reduce errors, and simplify Redux code.\n![image](https://github.com/user-attachments/assets/3165dc8f-4ee8-42dc-8d3e-08a47e698770)\n\u003cimg width=\"945\" alt=\"image\" src=\"https://github.com/user-attachments/assets/b554859b-dad8-478b-a24f-70d6a57a3920\" /\u003e\n\u003cimg width=\"935\" alt=\"image\" src=\"https://github.com/user-attachments/assets/248f4354-4b55-4584-ad52-9b048c43aeeb\" /\u003e\n### Without thunks:\n```js\n//src\u003efetaures\u003ecartSlice.js\n\nimport { createSlice } from \"@reduxjs/toolkit\";\n\nconst cartSlice = createSlice({\n  name: \"cart\",\n  initialState: {\n    items: [],\n    totalQuantity: 0,\n  },\n  reducers: {\n    addItem(state, action) {\n      const item = action.payload;\n      const existingItem = state.items.find((i) =\u003e i.id === item.id); // Check existing item\n      state.totalQuantity++; // Increment totalQuantity\n      if (!existingItem) {\n        // Add a new item if not already present\n        state.items.push({\n          id: item.id,\n          price: item.price,\n          quantity: 1,\n          totalPrice: item.price,\n          name: item.title,\n        });\n      } else {\n        // Increase quantity and update total price\n        existingItem.quantity++;\n        existingItem.totalPrice += item.price;\n      }\n    },\n\n    removeItem(state, action) {\n      const itemId = action.payload;\n      const existingItem = state.items.find((i) =\u003e i.id === itemId);\n      if (!existingItem) return; // Safety check\n      state.totalQuantity--;\n      if (existingItem.quantity === 1) {\n        state.items = state.items.filter((i) =\u003e i.id !== itemId);\n      } else {\n        existingItem.quantity--;\n        existingItem.totalPrice -= existingItem.price;\n      }\n    },\n  },\n});\n\nexport const cartActions = cartSlice.actions;\nexport default cartSlice;\n```\n```js\n//src\u003eApp.js\n\nimport { useDispatch, useSelector } from \"react-redux\";\nimport Cart from \"./components/Cart/Cart\";\nimport Layout from \"./components/Layout/Layout\";\nimport Products from \"./components/Shop/Products\";\nimport { useEffect } from \"react\";\nimport { uiActions } from \"./features/uiSlice\";\nimport Notification from \"./components/UI/Notification\";\n\nlet isInitial = true;\n\nfunction App() {\n  const dispatch = useDispatch();\n  const showCart = useSelector((store) =\u003e store.ui.cartIsVisible);\n  const cart = useSelector((store) =\u003e store.cart);\n  console.log(\"cartIsVisible:\", showCart);\n  const notification = useSelector((store) =\u003e store.ui.notification); // Corrected here to 'notification'\n\n  useEffect(() =\u003e {\n    const sendCartData = async () =\u003e {\n      dispatch(\n        uiActions.showNotification({\n          status: \"pending\",\n          title: \"Sending request.. ⏳\",\n          message: \"Sending cart data! 🛒⌛\",\n        })\n      );\n\n      const response = await fetch(\n        \"https://redux-academind-default-rtdb.asia-southeast1.firebasedatabase.app/cart.json\",\n        {\n          method: \"PUT\",\n          body: JSON.stringify(cart),\n        }\n      );\n\n      if (!response.ok) {\n        throw new Error(\"Sending cart data failed ⚠️\");\n      }\n\n      // DISPATCH success notificationn\n      dispatch(\n        uiActions.showNotification({\n          status: \"success\",\n          title: \"Success! 🎉\",\n          message: \"Cart data sent successfully ✅🛒\",\n        })\n      );\n    };\n\n    if (isInitial) {\n      isInitial = false;\n      return;\n    }\n\n    sendCartData().catch((error) =\u003e {\n      // DISPATCH error notification\n      dispatch(\n        uiActions.showNotification({\n          status: \"error\",\n          title: \"Error!⚠️\",\n          message: \"Sending cart data failed❌⚠️\",\n        })\n      );\n    });\n  }, [cart, dispatch]);\n\n  return (\n    \u003c\u003e\n      \u003cLayout\u003e\n        {/* Render Notification */}\n        {notification \u0026\u0026 (\n          \u003cNotification\n            status={notification.status}\n            title={notification.title}\n            message={notification.message}\n          /\u003e\n        )}\n        {showCart \u0026\u0026 \u003cCart /\u003e}\n        \u003cProducts /\u003e\n      \u003c/Layout\u003e\n    \u003c/\u003e\n  );\n}\n\nexport default App;\n```\nIn **Redux Toolkit**, handling asynchronous code (like API calls or side effects) is typically done using the following approaches:\n\n---\n\n### 1. **Redux Toolkit's `createAsyncThunk`**  \n`createAsyncThunk` is the most common and recommended way to handle async logic. It simplifies asynchronous actions by allowing you to write **async/await** logic without dealing with boilerplate.\n\n#### **Example**: Fetching data from an API\n\n```javascript\nimport { createSlice, createAsyncThunk } from \"@reduxjs/toolkit\";\n\n// Async action creator\nexport const fetchProducts = createAsyncThunk(\n  \"products/fetchProducts\", // Action type\n  async (_, { rejectWithValue }) =\u003e {\n    try {\n      const response = await fetch(\"https://fakestoreapi.com/products\");\n      if (!response.ok) {\n        throw new Error(\"Failed to fetch products\");\n      }\n      const data = await response.json();\n      return data; // Automatically dispatched as `fulfilled` action\n    } catch (error) {\n      return rejectWithValue(error.message); // Handle errors\n    }\n  }\n);\n\nconst productsSlice = createSlice({\n  name: \"products\",\n  initialState: {\n    products: [],\n    loading: false,\n    error: null,\n  },\n  extraReducers: (builder) =\u003e {\n    builder\n      .addCase(fetchProducts.pending, (state) =\u003e {\n        state.loading = true;\n        state.error = null;\n      })\n      .addCase(fetchProducts.fulfilled, (state, action) =\u003e {\n        state.loading = false;\n        state.products = action.payload;\n      })\n      .addCase(fetchProducts.rejected, (state, action) =\u003e {\n        state.loading = false;\n        state.error = action.payload || \"Something went wrong\";\n      });\n  },\n});\n\nexport default productsSlice.reducer;\n```\n\n#### **Usage in Components**:\n```javascript\nimport React, { useEffect } from \"react\";\nimport { useDispatch, useSelector } from \"react-redux\";\nimport { fetchProducts } from \"./productsSlice\";\n\nconst ProductList = () =\u003e {\n  const dispatch = useDispatch();\n  const { products, loading, error } = useSelector((state) =\u003e state.products);\n\n  useEffect(() =\u003e {\n    dispatch(fetchProducts());\n  }, [dispatch]);\n\n  if (loading) return \u003cp\u003eLoading...\u003c/p\u003e;\n  if (error) return \u003cp\u003eError: {error}\u003c/p\u003e;\n\n  return (\n    \u003cul\u003e\n      {products.map((product) =\u003e (\n        \u003cli key={product.id}\u003e{product.title}\u003c/li\u003e\n      ))}\n    \u003c/ul\u003e\n  );\n};\n\nexport default ProductList;\n```\n\n---\n\n### 2. **Middleware like Redux Thunk**  \nUnder the hood, `createAsyncThunk` uses **Redux Thunk** middleware. If you prefer manually handling async logic with Thunks, you can write them explicitly.\n\n#### **Example**: Manual Redux Thunk\n```javascript\n// Actions\nexport const fetchProducts = () =\u003e {\n  return async (dispatch) =\u003e {\n    dispatch({ type: \"products/fetchPending\" });\n    try {\n      const response = await fetch(\"https://fakestoreapi.com/products\");\n      const data = await response.json();\n      dispatch({ type: \"products/fetchFulfilled\", payload: data });\n    } catch (error) {\n      dispatch({ type: \"products/fetchRejected\", payload: error.message });\n    }\n  };\n};\n```\n\n---\n\n### 3. **RTK Query** (Advanced Option)  \nIf you're building APIs and require an **automatic caching and data fetching solution**, Redux Toolkit provides **RTK Query**. It simplifies data fetching and caching.\n\n#### **Setup Example**:\n```javascript\nimport { createApi, fetchBaseQuery } from \"@reduxjs/toolkit/query/react\";\n\nexport const productsApi = createApi({\n  reducerPath: \"productsApi\",\n  baseQuery: fetchBaseQuery({ baseUrl: \"https://fakestoreapi.com\" }),\n  endpoints: (builder) =\u003e ({\n    getProducts: builder.query({\n      query: () =\u003e \"/products\",\n    }),\n  }),\n});\n\nexport const { useGetProductsQuery } = productsApi;\n```\n\n#### **Usage in Components**:\n```javascript\nimport { useGetProductsQuery } from \"./productsApi\";\n\nconst ProductList = () =\u003e {\n  const { data: products, error, isLoading } = useGetProductsQuery();\n\n  if (isLoading) return \u003cp\u003eLoading...\u003c/p\u003e;\n  if (error) return \u003cp\u003eError: {error.message}\u003c/p\u003e;\n\n  return (\n    \u003cul\u003e\n      {products.map((product) =\u003e (\n        \u003cli key={product.id}\u003e{product.title}\u003c/li\u003e\n      ))}\n    \u003c/ul\u003e\n  );\n};\n\nexport default ProductList;\n```\n\n---\n\n### Summary: When to Use What?\n1. **`createAsyncThunk`**: Ideal for most async tasks like API requests with error handling.\n2. **Custom Thunks**: Use if you want manual control or more flexibility.\n3. **RTK Query**: Best for advanced use cases where data fetching, caching, and re-fetching are required.\n\nFor most projects, **`createAsyncThunk`** or **RTK Query** is sufficient and recommended.\n**Thunks** are a concept in programming that allow you to handle **asynchronous operations** (like API calls, timers, or side effects) in a **synchronous flow**. In the context of **Redux**, a \"thunk\" is a **middleware function** that helps you handle async logic in your actions and provides more control over dispatching actions.\n\n---\n\n### **What Problem Do Thunks Solve?**\n\nRedux's basic flow is **synchronous**, which means actions are dispatched, and reducers update the state immediately. However, many real-world applications require **asynchronous operations**, like:\n\n- Fetching data from an API\n- Posting data to a server\n- Performing timeouts or delays\n\nRedux itself does not support async logic out of the box. Thunks help bridge this gap.\n\n---\n\n### **What is a Thunk?**\n\nA **Thunk** is a function that wraps an **expression** (or logic) and delays its execution. In Redux, thunks are functions that **return another function** instead of an action object.\n\n- The returned function gets access to two important arguments:\n  - **`dispatch`**: Allows you to dispatch actions (e.g., success or error).\n  - **`getState`**: Lets you access the Redux store's current state.\n\n---\n\n### **How Does Redux Thunk Work?**\n\nWhen you use the **`redux-thunk`** middleware, it intercepts actions you dispatch. If the dispatched action is a **function** (instead of a plain object), the middleware executes that function. This allows you to write **async logic** inside your action creators.\n\n---\n\n### **Example of a Basic Redux Thunk**\n\n#### **1. Install Redux Thunk Middleware**\n```bash\nnpm install redux-thunk\n```\n\n#### **2. Configure Middleware in Your Store**\n```javascript\nimport { configureStore } from \"@reduxjs/toolkit\";\nimport thunk from \"redux-thunk\";\nimport cartReducer from \"./cartSlice\";\n\nconst store = configureStore({\n  reducer: { cart: cartReducer },\n  middleware: (getDefaultMiddleware) =\u003e getDefaultMiddleware().concat(thunk),\n});\n\nexport default store;\n```\n\n#### **3. Write Thunk Logic**\n\nHere's a simple example of an async thunk to fetch data from an API.\n\n```javascript\n// Action Creator with Thunk\nexport const fetchProducts = () =\u003e {\n  // This function is intercepted by redux-thunk\n  return async (dispatch, getState) =\u003e {\n    dispatch({ type: \"products/fetchPending\" }); // Set loading state\n\n    try {\n      const response = await fetch(\"https://fakestoreapi.com/products\");\n\n      if (!response.ok) {\n        throw new Error(\"Failed to fetch products!\");\n      }\n\n      const data = await response.json();\n\n      // Dispatch success action\n      dispatch({ type: \"products/fetchFulfilled\", payload: data });\n    } catch (error) {\n      // Dispatch error action\n      dispatch({ type: \"products/fetchRejected\", payload: error.message });\n    }\n  };\n};\n```\n\nIn this example:\n\n1. The **`fetchProducts`** action creator doesn't immediately return an action object.  \n2. It returns a function that performs an API call using **`async/await`**.  \n3. The **dispatch** function is used inside the async function to trigger \"pending,\" \"success,\" and \"error\" actions.\n\n---\n\n### **4. Update Reducer to Handle Actions**\n```javascript\nconst initialState = {\n  products: [],\n  loading: false,\n  error: null,\n};\n\nconst productsReducer = (state = initialState, action) =\u003e {\n  switch (action.type) {\n    case \"products/fetchPending\":\n      return { ...state, loading: true, error: null };\n    case \"products/fetchFulfilled\":\n      return { ...state, loading: false, products: action.payload };\n    case \"products/fetchRejected\":\n      return { ...state, loading: false, error: action.payload };\n    default:\n      return state;\n  }\n};\n\nexport default productsReducer;\n```\n\n---\n\n### **5. Use Thunk in a React Component**\n\n```javascript\nimport React, { useEffect } from \"react\";\nimport { useDispatch, useSelector } from \"react-redux\";\nimport { fetchProducts } from \"./productsActions\";\n\nconst Products = () =\u003e {\n  const dispatch = useDispatch();\n  const { products, loading, error } = useSelector((state) =\u003e state.products);\n\n  useEffect(() =\u003e {\n    dispatch(fetchProducts()); // Dispatch the thunk\n  }, [dispatch]);\n\n  if (loading) return \u003cp\u003eLoading...\u003c/p\u003e;\n  if (error) return \u003cp\u003eError: {error}\u003c/p\u003e;\n\n  return (\n    \u003cul\u003e\n      {products.map((product) =\u003e (\n        \u003cli key={product.id}\u003e{product.title}\u003c/li\u003e\n      ))}\n    \u003c/ul\u003e\n  );\n};\n\nexport default Products;\n```\n\n---\n\n### **Why Thunks Are Useful**\n\n1. **Handles Asynchronous Code**: Thunks allow you to perform async operations (e.g., API calls, timers) in Redux.  \n2. **Sequential Actions**: You can dispatch multiple actions in sequence (e.g., pending, success, and error states).  \n3. **Access to State**: The **`getState`** argument lets you read the current state inside your thunk logic.  \n4. **Avoid Boilerplate**: Thunks reduce boilerplate compared to writing everything manually.\n\n---\n\n### **Thunk vs Regular Action Creator**\n\n| **Regular Action Creator**                 | **Thunk Action Creator**                     |\n|-------------------------------------------|---------------------------------------------|\n| Returns a plain action object             | Returns a function instead of an object      |\n| Synchronous only                          | Can handle asynchronous logic                |\n| No access to `dispatch` or `getState`     | Has access to `dispatch` and `getState`      |\n| Example: `{ type: 'ACTION_TYPE', payload }`| Example: `return (dispatch) =\u003e { ... }`      |\n\n---\n\n### **Modern Alternative to Thunks**\n\n- While **thunks** are widely used, Redux Toolkit introduced **`createAsyncThunk`** (which internally uses thunks) to simplify async code even further. It reduces boilerplate and handles common async patterns automatically.\n\nFor example:\n```javascript\nexport const fetchProducts = createAsyncThunk(\"products/fetchProducts\", async () =\u003e {\n  const response = await fetch(\"https://fakestoreapi.com/products\");\n  return await response.json();\n});\n```\n\n---\n\n### **Conclusion**\n\nThunks are a powerful way to handle asynchronous code in Redux. They are middleware functions that allow you to perform async operations (like API calls) and dispatch actions based on the result (e.g., \"pending,\" \"success,\" \"error\"). While you can write thunks manually, tools like `createAsyncThunk` make it even easier to manage async logic.\nUsing `useEffect()` for async code like API calls is not *wrong*, but it has limitations compared to handling side effects using Redux Thunks or similar middleware. While both approaches can work, using `useEffect()` has several downsides in the context of **global state management** with Redux. Let’s break this down:\n\n---\n\n### **1. Separation of Concerns**\n**Problem with `useEffect()`**:\n- `useEffect` is designed for **local side effects** in a component, not global state management.\n- If you fetch data or handle async logic directly in `useEffect()`, your **component logic becomes tightly coupled with your async logic**, making the code harder to test and maintain.\n\n**With Thunks**:\n- Thunks separate the **business logic** (API calls, async tasks) from the **UI components**.\n- This makes your components cleaner, reusable, and easier to test, since the logic resides in Redux actions or thunks.\n\n**Example of tightly coupled useEffect logic**:\n```jsx\nuseEffect(() =\u003e {\n  const fetchData = async () =\u003e {\n    const response = await fetch('API_URL');\n    const data = await response.json();\n    setState(data); // Local or global state\n  };\n  fetchData();\n}, []);\n```\n\nWith thunks, the async code would live outside the component:\n```jsx\nuseEffect(() =\u003e {\n  dispatch(fetchDataThunk()); // Async logic is abstracted away\n}, [dispatch]);\n```\n\n---\n\n### **2. Code Duplication**\n**Problem with `useEffect()`**:\n- If multiple components need the same async data (like fetching user data), you must duplicate the `useEffect()` logic across components.\n\n**With Thunks**:\n- A single thunk can fetch the data and store it in the Redux store. Multiple components can access the **same global state** using `useSelector`.\n\n**Example**:\n```jsx\n// Repeated in multiple components\nuseEffect(() =\u003e {\n  const fetchUser = async () =\u003e {\n    const response = await fetch('/api/user');\n    const userData = await response.json();\n    setUser(userData);\n  };\n  fetchUser();\n}, []);\n```\n\nWith Redux Thunks:\n- You fetch the user data **once** in a thunk and share it globally:\n```javascript\nexport const fetchUser = () =\u003e async (dispatch) =\u003e {\n  const response = await fetch('/api/user');\n  const data = await response.json();\n  dispatch(userActions.setUser(data));\n};\n```\nComponents just use:\n```jsx\nconst user = useSelector((state) =\u003e state.user);\n```\n\n---\n\n### **3. Limited Control Over Dispatching Actions**\n**Problem with `useEffect()`**:\n- `useEffect` alone cannot handle intermediate states like **loading, success, and error** easily without adding extra flags (`isLoading`, `isError`, etc.) locally.\n\n**With Thunks**:\n- You can dispatch multiple actions to handle loading, success, and error states.\n\n**Example**:\n```javascript\nexport const fetchData = () =\u003e async (dispatch) =\u003e {\n  dispatch({ type: 'fetch/pending' }); // Set loading state\n  try {\n    const response = await fetch('/api/data');\n    const data = await response.json();\n    dispatch({ type: 'fetch/success', payload: data });\n  } catch (error) {\n    dispatch({ type: 'fetch/error', payload: error.message });\n  }\n};\n```\nComponents handle the global state:\n```jsx\nconst { data, isLoading, error } = useSelector((state) =\u003e state.fetch);\n```\n\nWith `useEffect`, this becomes repetitive and harder to maintain.\n\n---\n\n### **4. Scalability Issues**\n**Problem with `useEffect()`**:\n- As your app grows, if every async operation is handled in `useEffect()`, it becomes unmanageable. Every component deals with its own logic for API calls, state management, and error handling.\n\n**With Thunks**:\n- Thunks centralize async logic in Redux, making it easier to scale. Adding or modifying async logic doesn't affect your components.\n\n---\n\n### **5. Testing**\n**Problem with `useEffect()`**:\n- Testing components with `useEffect()` requires mocking the entire network request, which can be cumbersome.\n\n**With Thunks**:\n- You can test thunks independently of your components using tools like **Jest** and **Mock Service Worker**.\n\nExample of testing a thunk:\n```javascript\nit('should dispatch success action after API call', async () =\u003e {\n  const dispatch = jest.fn();\n  const mockResponse = { data: [1, 2, 3] };\n\n  global.fetch = jest.fn(() =\u003e\n    Promise.resolve({ json: () =\u003e Promise.resolve(mockResponse) })\n  );\n\n  await fetchData()(dispatch);\n  expect(dispatch).toHaveBeenCalledWith({ type: 'fetch/pending' });\n  expect(dispatch).toHaveBeenCalledWith({ type: 'fetch/success', payload: mockResponse });\n});\n```\n\n---\n\n### **Summary: When to Use Thunks Instead of `useEffect()`**\n\n| **Criteria**                   | **useEffect**                                     | **Redux Thunks**                              |\n|--------------------------------|--------------------------------------------------|----------------------------------------------|\n| **Scope**                      | Local to the component                           | Global async logic (reusable across app)     |\n| **Code Reusability**           | Requires duplicating logic in multiple places    | Centralized logic, reusable everywhere       |\n| **Error Handling**             | Needs manual flags for loading/error states      | Handles multiple states with dispatch actions|\n| **State Management**           | Local or tightly coupled to the component        | Global state with Redux                      |\n| **Testing**                    | Harder to test, tightly coupled to UI            | Thunks are easier to test independently      |\n| **Scalability**                | Becomes unmanageable in large apps               | Scalable and maintainable                    |\n\n---\n\n### **When Is `useEffect()` Acceptable?**\n- Small components with **local state**.\n- Simple side effects that don’t need to be reused globally.\n- For small projects where Redux is unnecessary.\n\n---\n\n### **Conclusion**\nWhile `useEffect()` works for basic side effects, using **Redux Thunks** is the preferred approach for handling **asynchronous operations** in applications with global state. Thunks offer cleaner separation of concerns, scalability, and better reusability, while keeping the components simple and focused on UI rendering.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fskyybbanerjee%2Fadvance_redux","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fskyybbanerjee%2Fadvance_redux","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fskyybbanerjee%2Fadvance_redux/lists"}