Using Redux Toolkit’s createAsyncThunk

While a Redux store possesses great state management features, it has no clue how to deal with asynchronous logic. Redux eschews handling asynchronous logic simply because it doesn’t know what you want to do with the data you fetched, let alone if it’s ever fetched — hello, errors. 🙂

Middleware has since been used in Redux applications to perform asynchronous tasks, with Redux Thunk’s middleware being the most popular package. A middleware is designed to enable developers to write logic that has side effects — which refers to any external interaction outside an existing client application, like fetching data from an API.

With Redux Toolkit, Redux Thunk is included by default, allowing createAsyncThunk to perform delayed, asynchronous logic before sending the processed result to the reducers.

In this article, you’ll learn how to use the createAsyncThunk API to perform asynchronous tasks in Redux apps.

Prerequisites

You’ll need to have some knowledge about Redux to understand Redux Toolkit. However, you can reference this post to learn how to create Redux apps with Redux Toolkit.

Understanding the function parameters

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

const initialState = {
  entities: [],
  loading: false,
}

const getPosts = createAsyncThunk(
  //action type string
  'posts/getPosts',
  // callback function
  async (thunkAPI) => {
    const res = await fetch('https://jsonplaceholder.typicode.com/posts').then(
    (data) => data.json()
  )
  return res
})


export const postSlice = createSlice({
  name: 'posts',
  initialState,
  reducers: {},
  extraReducers: {},
})

export const postReducer = postSlice.reducer

The file above is a Redux slice in a React app. A slice is a function that contains your store and reducer functions used to modify store data. The createSlice API is set to be the norm for writing Redux logic.

Within createSlice, synchronous requests made to the store are handled in the reducers object while extraReducers handles asynchronous requests, which is our main focus.

Asynchronous requests created with createAsyncThunk accept three parameters: an action type string, a callback function (referred to as a payloadCreator), and an options object.

Taking the previous code block as a Redux store for a blog application, let’s examine getPosts:

const getPosts = createAsyncThunk(
  'posts/getPosts',
  async (thunkAPI) => {
    const res = await fetch('https://jsonplaceholder.typicode.com/posts').then(
    (data) => data.json()
  )
  return res
})

posts/getPosts is the action type string in this case. Whenever this function is dispatched from a component within our application, createAsyncThunk generates promise lifecycle action types using this string as a prefix:

  • pending: posts/getPosts/pending
  • fulfilled: posts/getPosts/fulfilled
  • rejected: posts/getPosts/rejected

On its initial call, createAsyncThunk dispatches the posts/getPosts/pending lifecycle action type. The payloadCreator then executes to return either a result or an error.

In the event of an error, posts/getPosts/rejected is dispatched and createAsyncThunk should either return a rejected promise containing an Error instance, a plain descriptive message, or a resolved promise with a RejectWithValue argument as returned by the thunkAPI.rejectWithValue function (more on thunkAPI and error handling momentarily).

If our data fetch is successful, the posts/getPosts/fulfilled action type gets dispatched.

The options parameter is an object containing different configurations for the createAsyncThunk API. View the list of available options.

The three lifecycle action types mentioned earlier can then be evaluated in extraReducers, where we make our desired changes to the store. In this case, let’s populate entities with some data and appropriately set the loading state in each action type:

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

const initialState = {
  entities: [],
  loading: false,
}

const getPosts = createAsyncThunk(
  'posts/getPosts',
  async (thunkAPI) => {
    const res = await fetch('https://jsonplaceholder.typicode.com/posts').then(
    (data) => data.json()
  )
  return res
})


export const postSlice = createSlice({
  name: 'posts',
  initialState,
  reducers: {},
  extraReducers: {
    [getPosts.pending]: (state) => {
      state.loading = true
    },
    [getPosts.fulfilled]: (state, { payload }) => {
      state.loading = false
      state.entities = payload
    },
    [getPosts.rejected]: (state) => {
      state.loading = false
    },
  },
})

export const postReducer = postSlice.reducer

If you’re new to Redux Toolkit, the state logic above might seem off to you. Redux Toolkit makes use of the Immer library, which allows developers to write mutable logic in reducer functions. Immer then converts your mutable logic to immutable logic under the hood.

Also, notice the function expression. For personal preference, I’ve used the map-object notation to handle the requests, mainly because this approach looks tidier.

The recommended way to handle requests is the builder callback notation because this approach has better TypeScript support (and thus IDE autocomplete even for JavaScript users).

N.B.: As your application grows, you will continue to make more asynchronous requests to your backend API and in turn handle their lifecycle action types. Consolidating all this logic into one file makes the file harder to read. I wrote an article on my approach to separating logic in your Redux Toolkit applications.

Dispatching actions in components

By using useSelector and useDispatch from react-redux, we can read state from a Redux store and dispatch any action from a component, respectively.

Let’s set up a component to dispatch getPosts when it mounts:

import { useEffect } from 'react'
import { useDispatch, useSelector } from 'react-redux'
import { getPosts } from '../redux/features/posts/postThunk'

export default function Home() {
  const dispatch = useDispatch()
  const { entities, loading } = useSelector((state) => state.posts)

  useEffect(() => {
    dispatch(getPosts())
  }, [])

  if (loading) return <p>Loading...</p>

  return (
    <div>
      <h2>Blog Posts</h2>
      {entities.map((post) => (
        <p key={post.id}>{post.title}</p>
      ))}
    </div>
  )
}

The Redux DevTools extension gives real-time information on the dispatch of any lifecycle action type.

It’s important to note that payloadCreator accepts only two parameters, one of them being a custom argument that may be used in your request and the other being thunkAPI. thunkAPI is an object containing all of the parameters that are normally passed to a Redux Thunk function — like dispatch and getState. Take a look at all the acceptable parameters.

If your request requires more than one parameter, you can pass in an object when you dispatch the reducer function:

dispatch(getPosts({ category: 'politics', sortBy: 'name' })

Handling errors in createAsyncThunk

Remember that when your payloadCreator returns a rejected promise, the rejected action is dispatched (with action.payload as undefined). Most times, we want to display custom error messages rather than the message returned in the Error object.

By using thunkAPI, you can return a resolved promise to the reducer, which has action.payload set to a custom value of your choice. thunkAPI uses its rejectWithValue property to perform this.

Let’s say we want to add a new post to the blog. Our createAsyncThunk function would look something like this:

const post = { title: 'lorem', body: 'ipsum' }

const addPost = createAsyncThunk(
  'posts/addPost',
  async (post, { rejectWithValue }) => {
    try {
      const response = await fetch(
        'https://jsonplaceholder.typicode.com/posts',
        {
          method: 'POST',
          body: JSON.stringify(post),
          header: {
            'Content-Type': 'application/json',
          },
        }
      )
      const data = await response.json()
      return data
    } catch (err) {
      // You can choose to use the message attached to err or write a custom error
      return rejectWithValue('Opps there seems to be an error')
    }
  }
)

Then evaluate posts/addPost/rejected in extraReducers:

extraReducers: {
  [addPost.rejected]: (state, action) => {
    // returns 'Opps there seems to be an error'
    console.log(action.payload) 
  }
}

We’ve come to a close here, devs. So far we’ve been able to go over the basic features of createAsyncThunk and see how it works with the reducers in the slice function. The API also has some more advanced topics like canceling requests, which you can read further on.

Conclusion

To conclude, I’d like to mention Redux Toolkit’s RTK Query data fetching API.

RTK Query is a purpose-built, data-fetching and caching solution for Redux apps, which can eliminate the need to write any thunks or reducers to manage data fetching. So if you’ve dabbled with a library like React Query, it would be wise to use RTK Query for asynchronous logic in Redux because the syntax is quite similar.

The post Using Redux Toolkit’s <code>createAsyncThunk</code> appeared first on LogRocket Blog.

This article was republished from its original source.
Call Us: 1(800)730-2416

Pixeldust is a 20-year-old web development agency specializing in Drupal and WordPress and working with clients all over the country. With our best in class capabilities, we work with small businesses and fortune 500 companies alike. Give us a call at 1(800)730-2416 and let’s talk about your project.

FREE Drupal SEO Audit

Test your site below to see which issues need to be fixed. We will fix them and optimize your Drupal site 100% for Google and Bing. (Allow 30-60 seconds to gather data.)

Powered by

Using Redux Toolkit’s createAsyncThunk

On-Site Drupal SEO Master Setup

We make sure your site is 100% optimized (and stays that way) for the best SEO results.

With Pixeldust On-site (or On-page) SEO we make changes to your site’s structure and performance to make it easier for search engines to see and understand your site’s content. Search engines use algorithms to rank sites by degrees of relevance. Our on-site optimization ensures your site is configured to provide information in a way that meets Google and Bing standards for optimal indexing.

This service includes:

  • Pathauto install and configuration for SEO-friendly URLs.
  • Meta Tags install and configuration with dynamic tokens for meta titles and descriptions for all content types.
  • Install and fix all issues on the SEO checklist module.
  • Install and configure XML sitemap module and submit sitemaps.
  • Install and configure Google Analytics Module.
  • Install and configure Yoast.
  • Install and configure the Advanced Aggregation module to improve performance by minifying and merging CSS and JS.
  • Install and configure Schema.org Metatag.
  • Configure robots.txt.
  • Google Search Console setup snd configuration.
  • Find & Fix H1 tags.
  • Find and fix duplicate/missing meta descriptions.
  • Find and fix duplicate title tags.
  • Improve title, meta tags, and site descriptions.
  • Optimize images for better search engine optimization. Automate where possible.
  • Find and fix the missing alt and title tag for all images. Automate where possible.
  • The project takes 1 week to complete.