Redux-thunk Cannot Read Property Then of Undefined
While a Redux store possesses smashing state management features, information technology has no clue how to deal with asynchronous logic. Redux eschews handling asynchronous logic only because information technology doesn't know what you want to practice with the data yous fetched, allow lone if information technology's ever fetched — hello, errors. 🙂
Middleware has since been used in Redux applications to perform asynchronous tasks, with Redux Thunk'south middleware existence the nigh popular package. A middleware is designed to enable developers to write logic that has side furnishings — which refers to whatsoever external interaction exterior an existing customer application, similar 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 upshot to the reducers.
In this article, y'all'll learn how to use the createAsyncThunk
API to perform asynchronous tasks in Redux apps.
Prerequisites
Yous'll need to have some knowledge about Redux to sympathize 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 office async (thunkAPI) => { const res = await fetch('https://jsonplaceholder.typicode.com/posts').and so( (data) => data.json() ) return res }) consign const postSlice = createSlice({ proper noun: 'posts', initialState, reducers: {}, extraReducers: {}, }) export const postReducer = postSlice.reducer
The file above is a Redux slice in a React app. A slice is a part that contains your store and reducer functions used to alter store data. The createSlice
API is set to be the norm for writing Redux logic.
Within createSlice
, synchronous requests fabricated to the store are handled in the reducers
object while extraReducers
handles asynchronous requests, which is our primary focus.
Asynchronous requests created with createAsyncThunk
accept three parameters: an action type cord, a callback office (referred to every bit a payloadCreator
), and an options object.
Taking the previous code block as a Redux store for a blog awarding, let's examine getPosts
:
const getPosts = createAsyncThunk( 'posts/getPosts', async (thunkAPI) => { const res = expect fetch('https://jsonplaceholder.typicode.com/posts').then( (information) => information.json() ) render res })
posts/getPosts
is the activity type cord in this instance. Whenever this function is dispatched from a component within our application, createAsyncThunk
generates promise lifecycle activity types using this cord as a prefix:
- awaiting:
posts/getPosts/pending
- fulfilled:
posts/getPosts/fulfilled
- rejected:
posts/getPosts/rejected
On its initial call, createAsyncThunk
dispatches the posts/getPosts/awaiting
lifecycle action blazon. The payloadCreator
and then executes to return either a outcome or an error.
In the event of an error, posts/getPosts/rejected
is dispatched and createAsyncThunk
should either return a rejected promise containing an Fault
instance, a plainly descriptive message, or a resolved promise with a RejectWithValue
argument equally returned by the thunkAPI.rejectWithValue
function (more than on thunkAPI
and fault handling momentarily).
If our data fetch is successful, the posts/getPosts/fulfilled
action blazon gets dispatched.
The options
parameter is an object containing unlike configurations for the createAsyncThunk
API. View the list of available options.
The three lifecycle action types mentioned earlier tin so be evaluated in extraReducers
, where we brand our desired changes to the store. In this case, let's populate entities
with some information and appropriately fix the loading state in each action blazon:
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) => { country.loading = true }, [getPosts.fulfilled]: (state, { payload }) => { state.loading = false state.entities = payload }, [getPosts.rejected]: (state) => { state.loading = faux }, }, }) export const postReducer = postSlice.reducer
If you're new to Redux Toolkit, the state logic in a higher place might seem off to you. Redux Toolkit makes utilise 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 role expression. For personal preference, I've used the map-object notation to handle the requests, mainly because this approach looks tidier.
The recommended mode to handle requests is the builder callback notation because this arroyo has amend TypeScript support (and thus IDE autocomplete fifty-fifty for JavaScript users).
N.B.: As your application grows, yous will proceed to brand more asynchronous requests to your backend API and in turn handle their lifecycle activity 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 tin read state from a Redux store and acceleration any action from a component, respectively.
Let'south set up upwards 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' consign default office Home() { const dispatch = useDispatch() const { entities, loading } = useSelector((country) => state.posts) useEffect(() => { dispatch(getPosts()) }, []) if (loading) render <p>Loading...</p> return ( <div> <h2>Blog Posts</h2> {entities.map((post) => ( <p key={postal service.id}>{mail service.title}</p> ))} </div> ) }
The Redux DevTools extension gives real-time information on the dispatch of whatever lifecycle action type.
It's of import to note that payloadCreator
accepts just two parameters, one of them beingness a custom statement that may be used in your request and the other being thunkAPI
. thunkAPI
is an object containing all of the parameters that are commonly passed to a Redux Thunk part — like acceleration
and getState
. Accept a expect at all the acceptable parameters.
If your asking requires more than than i parameter, yous tin can pass in an object when you dispatch the reducer function:
dispatch(getPosts({ category: 'politics', sortBy: 'proper noun' })
Handling errors in createAsyncThunk
Think that when your payloadCreator
returns a rejected promise, the rejected
activity is dispatched (with activity.payload
every bit undefined
). Most times, we want to brandish custom mistake messages rather than the bulletin returned in the Error
object.
By using thunkAPI
, you can return a resolved promise to the reducer, which has activity.payload
set to a custom value of your pick. thunkAPI
uses its rejectWithValue
holding to perform this.
Let's say we want to add a new post to the blog. Our createAsyncThunk
office 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', trunk: JSON.stringify(post), header: { 'Content-Blazon': 'awarding/json', }, } ) const data = await response.json() return data } take hold of (err) { // You can choose to use the bulletin attached to err or write a custom error return rejectWithValue('Opps there seems to be an error') } } )
And then evaluate posts/addPost/rejected
in extraReducers
:
extraReducers: { [addPost.rejected]: (state, action) => { // returns 'Opps there seems to be an error' console.log(action.payload) } }
Nosotros've come to a close hither, devs. So far nosotros've been able to go over the bones features of createAsyncThunk
and see how it works with the reducers in the piece function. The API likewise has some more than advanced topics like canceling requests, which you lot tin read further on.
Decision
To conclude, I'd similar to mention Redux Toolkit'due south RTK Query information fetching API.
RTK Query is a purpose-built, data-fetching and caching solution for Redux apps, which tin can eliminate the need to write any thunks or reducers to manage data fetching. And so if you've dabbled with a library similar React Query, it would be wise to use RTK Query for asynchronous logic in Redux because the syntax is quite like.
LogRocket: Full visibility into your web apps
LogRocket is a frontend application monitoring solution that lets you replay problems as if they happened in your ain browser. Instead of guessing why errors happen, or asking users for screenshots and log dumps, LogRocket lets yous replay the session to apace understand what went wrong. It works perfectly with any app, regardless of framework, and has plugins to log boosted context from Redux, Vuex, and @ngrx/store.
In addition to logging Redux actions and state, LogRocket records console logs, JavaScript errors, stacktraces, network requests/responses with headers + bodies, browser metadata, and custom logs. Information technology too instruments the DOM to tape the HTML and CSS on the page, recreating pixel-perfect videos of fifty-fifty the well-nigh complex single-page and mobile apps.
Try it for costless.
meyersbrestiong1961.blogspot.com
Source: https://blog.logrocket.com/using-redux-toolkits-createasyncthunk/
0 Response to "Redux-thunk Cannot Read Property Then of Undefined"
Post a Comment