VOOZH about

URL: https://dev.to/ola_1313/1-rn-thing-a-day-day-13-redux-thunk-209i

⇱ 1 RN Thing a Day – Day 13: Redux Thunk - DEV Community


What Is Redux Thunk?
Redux Thunk is a middleware for Redux that allows you to write action creators that return a function instead of a plain action object.


Normally, Redux actions look like this:

{
 type: "SET_USER",
 payload: user
}

async code doesn't work directly: Redux would throw an error because it expects an object.

dispatch(async () => { 
 const data = await api.getUser()
})

But with thunk, you can return a function:

const fetchUser = () => {// Return an async function that receives dispatch as parameter
 return async (dispatch) => {
 const response = await api.getUser()

 dispatch({
 type: "SET_USER",
 payload: response.data
 })
 }
}

Thunk acts as the bridge between async operations and Redux state updates.

The Problem Without Thunk
Imagine handling login directly inside a screen:

const handleLogin = async () => {
 setLoading(true)

 try {
 const response = await api.login(email, password)

 dispatch({
 type: "LOGIN_SUCCESS",
 payload: response.data
 })
 } catch (error) {
 setError(error.message)
 }

 setLoading(false)
}

This approach causes problems:

  • Components become bloated
  • Logic is duplicated
  • Reusability decreases
  • Testing becomes harder
  • UI and business logic become tightly coupled

The Same Logic Using Thunk:

export const loginUser = (email, password) => {
 return async (dispatch) => {
 dispatch(setLoading(true))

 try {
 const response = await api.login(email, password)

 dispatch(loginSuccess(response.data))
 } catch (error) {
 dispatch(loginError(error.message))
 } finally {
 dispatch(setLoading(false))
 }
 }
}

Then inside the component:

dispatch(loginUser(email, password))

Now the component becomes clean and focused only on UI.


Redux Toolkit createAsyncThunk

Modern Redux apps should prefer Redux Toolkit. Instead of manually writing thunk boilerplate.

The Structure Usually:

  • Service Layer: Handles API only.
// services/userService.js

export const getProducts = async () => {
 const response = await fetch(API_URL)

 return response.json()
}
  • Thunk Layer: Handles async Redux logic.
// store/thunks/userThunk.js

export const fetchProducts = createAsyncThunk(
 "products/fetchProducts", // action type prefix. // sliceName/actionName

 async () => {
 return await getProducts()
 }
)

"products/fetchProducts" It is simply:a unique Redux action identifier prefix used to generate async action types automatically.

  • Slice Layer: Handles state updates.
import { createSlice } from "@reduxjs/toolkit"
import { fetchProducts } from "./productThunk"

const productSlice = createSlice({
 name: "products",

 initialState: {
 products: [],
 loading: false,
 error: null
 },

 reducers: {},

 extraReducers: (builder) => {
 builder
 .addCase(fetchProducts.pending, (state) => { /
 state.loading = true
 })

 .addCase(fetchProducts.fulfilled, (state, action) => {
//internally matches:products/fetchProducts/fulfilled
 state.loading = false
 state.products = action.payload
 })

 .addCase(fetchProducts.rejected, (state, action) => {
 state.loading = false
 state.error = action.error.message
 })
 }
})

export default productSlice.reducer

Benefits:

  • Cleaner syntax
  • Built-in pending/fulfilled/rejected states
  • Less boilerplate
  • Better TypeScript support

Important Concept
createAsyncThunk is NOT mainly about API calls.
It is about:
Managing async operations that affect global state. API calls are just the most common example.

Better understanding:
Thunk = async business logic connected to Redux state

That business logic may include:

  • API calls
  • caching
  • conditional fetching
  • retries
  • authentication
  • reading current state
  • dispatching multiple actions