createAsyncThunk
概要
Reduxアクションタイプ文字列と、Promiseを返す必要のあるコールバック関数を受け取る関数です。渡されたアクションタイプのプレフィックスに基づいて、Promiseライフサイクルアクションタイプを生成し、Promiseコールバックを実行し、返されたPromiseに基づいてライフサイクルアクションをディスパッチするサンクアクションクリエーターを返します。
これは、非同期リクエストライフサイクルを処理するための標準的な推奨アプローチを抽象化したものです。
どのデータをフェッチしているか、読み込み状態をどのように追跡したいか、返されたデータをどのように処理する必要があるかを知らないため、Reducer関数は生成しません。 これらのアクションを処理する独自のReducerロジックを、アプリケーションに適した読み込み状態と処理ロジックで記述する必要があります。
Redux ToolkitのRTK QueryデータフェッチAPIは、Reduxアプリケーション向けの専用データフェッチおよびキャッシュソリューションであり、データフェッチを管理するためのサンクまたはReducerを記述する必要性を排除できます。ぜひお試しいただき、アプリケーションのデータフェッチコードを簡素化できるかどうかをご確認ください。
使用例
import { createAsyncThunk, createSlice } from '@reduxjs/toolkit'
import { userAPI } from './userAPI'
// First, create the thunk
const fetchUserById = createAsyncThunk(
'users/fetchByIdStatus',
async (userId: number, thunkAPI) => {
const response = await userAPI.fetchById(userId)
return response.data
},
)
interface UsersState {
entities: User[]
loading: 'idle' | 'pending' | 'succeeded' | 'failed'
}
const initialState = {
entities: [],
loading: 'idle',
} satisfies UserState as UsersState
// Then, handle actions in your reducers:
const usersSlice = createSlice({
name: 'users',
initialState,
reducers: {
// standard reducer logic, with auto-generated action types per reducer
},
extraReducers: (builder) => {
// Add reducers for additional action types here, and handle loading state as needed
builder.addCase(fetchUserById.fulfilled, (state, action) => {
// Add user to the state array
state.entities.push(action.payload)
})
},
})
// Later, dispatch the thunk as needed in the app
dispatch(fetchUserById(123))
パラメータ
createAsyncThunk
は、文字列アクションのtype
値、payloadCreator
コールバック、およびoptions
オブジェクトの3つのパラメータを受け取ります。
type
非同期リクエストのライフサイクルを表す追加のReduxアクションタイプ定数を生成するために使用される文字列
たとえば、'users/requestStatus'
のtype
引数は、次のアクションタイプを生成します
pending
:'users/requestStatus/pending'
fulfilled
:'users/requestStatus/fulfilled'
rejected
:'users/requestStatus/rejected'
payloadCreator
非同期ロジックの結果を含むPromiseを返す必要のあるコールバック関数。 同期的に値を返すこともできます。 エラーが発生した場合、`Error`インスタンスを含むrejected Promise、または記述的なエラーメッセージなどのプレーンな値、あるいは`thunkAPI.rejectWithValue`関数によって返される`RejectWithValue`引数を持つresolved Promiseを返す必要があります。
payloadCreator
関数は、適切な結果を計算するために必要なロジックを自由に含めることができます。 これには、標準的なAJAXデータフェッチリクエスト、結果が最終的な値に結合された複数のAJAXコール、React Native AsyncStorage
との相互作用などが含まれます。
payloadCreator
関数は、2つの引数で呼び出されます
arg
: サンクアクションクリエーターがディスパッチされたときに渡された最初のパラメーターを含む単一の値。 リクエストの一部として必要なアイテムIDなどの値を渡すのに役立ちます。 複数の値を渡す必要がある場合は、サンクをディスパッチするときに、`dispatch(fetchUsers({status: 'active', sortBy: 'name'}))`のようにオブジェクトにまとめて渡します。thunkAPI
: 通常はReduxサンク関数に渡されるすべてのパラメーターと、追加のオプションを含むオブジェクトdispatch
: Reduxストアのdispatch
メソッドgetState
: ReduxストアのgetState
メソッドextra
: サンクミドルウェアにセットアップ時に渡された「追加引数」(存在する場合)requestId
: このリクエストシーケンスを識別するために自動的に生成された一意の文字列ID値signal
: アプリケーションロジックの別の部分がこのリクエストをキャンセルする必要があるとマークしているかどうかを確認するために使用できるAbortController.signal
オブジェクト。rejectWithValue(value, [meta])
: rejectWithValueは、定義されたペイロードとメタを持つrejectedレスポンスを返すために、アクションクリエーターで`return`(または`throw`)できるユーティリティ関数です。 指定した値を渡し、rejectedアクションのペイロードで返します。 `meta`も渡すと、既存の`rejectedAction.meta`とマージされます。fulfillWithValue(value, meta)
: fulfillWithValueは、`fulfilledAction.meta`に追加できる機能を持ちながら、値で`fulfill`するために、アクションクリエーターで`return`できるユーティリティ関数です。
payloadCreator
関数のロジックは、結果を計算するために必要に応じてこれらの値を使用できます。
オプション
次のオプションフィールドを持つオブジェクト
condition(arg, { getState, extra } ): boolean | Promise<boolean>
: 必要に応じて、ペイロードクリエーターの実行とすべてのアクションのディスパッチをスキップするために使用できるコールバック。 詳細な説明については、実行前のキャンセルを参照してください。dispatchConditionRejection
:condition()
がfalse
を返した場合、デフォルトの動作ではアクションはまったくディスパッチされません。 サンクがキャンセルされたときに「rejected」アクションをディスパッチしたい場合は、このフラグをtrue
に設定します。idGenerator(arg): string
: リクエストシーケンスのrequestId
を生成するときに使用する関数。 デフォルトではnanoidを使用しますが、独自のID生成ロジックを実装できます。serializeError(error: unknown) => any
内部`miniSerializeError`メソッドを独自のシリアライズロジックに置き換えます。getPendingMeta({ arg, requestId }, { getState, extra }): any
: `pendingAction.meta`フィールドにマージされるオブジェクトを作成する関数。
戻り値
createAsyncThunk
は、標準のReduxサンクアクションクリエーターを返します。 サンクアクションクリエーター関数には、`pending`、`fulfilled`、`rejected`の場合のプレーンアクションクリエーターがネストされたフィールドとしてアタッチされます。
上記の`fetchUserById`の例を使用すると、`createAsyncThunk`は4つの関数を生成します
fetchUserById
、記述した非同期ペイロードコールバックを開始するサンクアクションクリエーターfetchUserById.pending
、`'users/fetchByIdStatus/pending'`アクションをディスパッチするアクションクリエーターfetchUserById.fulfilled
、`'users/fetchByIdStatus/fulfilled'`アクションをディスパッチするアクションクリエーターfetchUserById.rejected
、`'users/fetchByIdStatus/rejected'`アクションをディスパッチするアクションクリエーター
ディスパッチされると、サンクは
pending
アクションをディスパッチしますpayloadCreator
コールバックを呼び出し、返されたPromiseが解決されるのを待ちます- Promiseが解決されると
- Promiseが正常に解決された場合、Promiseの値を`action.payload`として`fulfilled`アクションをディスパッチします
- Promiseが`rejectWithValue(value)`戻り値で解決された場合、`action.payload`に渡された値と`action.error.message`として'Rejected'を使用して`rejected`アクションをディスパッチします
- Promiseが失敗し、`rejectWithValue`で処理されなかった場合、エラー値のシリアライズされたバージョンを`action.error`として`rejected`アクションをディスパッチします
- 最終的にディスパッチされたアクション(`fulfilled`または`rejected`アクションオブジェクト)を含むfulfilled Promiseを返します
Promiseライフサイクルアクション
createAsyncThunk
は、createAction
を使用して3つのReduxアクションクリエーターを生成します:pending
、fulfilled
、およびrejected
。 各ライフサイクルアクションクリエーターは、返されたサンクアクションクリエーターにアタッチされるため、Reducerロジックはアクションタイプを参照し、ディスパッチされたときにアクションに応答できます。 各アクションオブジェクトには、`action.meta`の下に現在の一意の`requestId`と`arg`値が含まれます。
アクションクリエーターには、次のシグネチャがあります
interface SerializedError {
name?: string
message?: string
code?: string
stack?: string
}
interface PendingAction<ThunkArg> {
type: string
payload: undefined
meta: {
requestId: string
arg: ThunkArg
}
}
interface FulfilledAction<ThunkArg, PromiseResult> {
type: string
payload: PromiseResult
meta: {
requestId: string
arg: ThunkArg
}
}
interface RejectedAction<ThunkArg> {
type: string
payload: undefined
error: SerializedError | any
meta: {
requestId: string
arg: ThunkArg
aborted: boolean
condition: boolean
}
}
interface RejectedWithValueAction<ThunkArg, RejectedValue> {
type: string
payload: RejectedValue
error: { message: 'Rejected' }
meta: {
requestId: string
arg: ThunkArg
aborted: boolean
}
}
type Pending = <ThunkArg>(
requestId: string,
arg: ThunkArg,
) => PendingAction<ThunkArg>
type Fulfilled = <ThunkArg, PromiseResult>(
payload: PromiseResult,
requestId: string,
arg: ThunkArg,
) => FulfilledAction<ThunkArg, PromiseResult>
type Rejected = <ThunkArg>(
requestId: string,
arg: ThunkArg,
) => RejectedAction<ThunkArg>
type RejectedWithValue = <ThunkArg, RejectedValue>(
requestId: string,
arg: ThunkArg,
) => RejectedWithValueAction<ThunkArg, RejectedValue>
Reducerでこれらのアクションを処理するには、 "builder callback"表記法を使用して、`createReducer`または`createSlice`でアクションクリエーターを参照します。
const reducer1 = createReducer(initialState, (builder) => {
builder.addCase(fetchUserById.fulfilled, (state, action) => {})
})
const reducer2 = createSlice({
name: 'users',
initialState,
reducers: {},
extraReducers: (builder) => {
builder.addCase(fetchUserById.fulfilled, (state, action) => {})
},
})
さらに、fulfilledアクションとrejectedアクションの両方に一致させるための`settled`マッチャーがアタッチされています。 概念的には、これは`finally`ブロックに似ています。
`settled`はアクションクリエーターではなくマッチャーであるため、`addCase`ではなく`addMatcher`を使用してください。
const reducer1 = createReducer(initialState, (builder) => {
builder.addMatcher(fetchUserById.settled, (state, action) => {})
})
const reducer2 = createSlice({
name: 'users',
initialState,
reducers: {},
extraReducers: (builder) => {
builder.addMatcher(fetchUserById.settled, (state, action) => {})
},
})
サンク結果の処理
Result アクションのアンラップ
Thunk はディスパッチされると値を返す場合があります。一般的なユースケースは、Thunk から Promise を返し、コンポーネントから Thunk をディスパッチし、Promise が解決するのを待ってから追加の作業を行うことです。
const onClick = () => {
dispatch(fetchUserById(userId)).then(() => {
// do additional work
})
}
createAsyncThunk
によって生成された Thunk は、**常に解決された Promise を返します**。Promise 内には、状況に応じて fulfilled
アクションオブジェクトまたは rejected
アクションオブジェクトが含まれます。
呼び出し元のロジックは、これらのアクションを元の Promise の内容であるかのように扱いたい場合があります。ディスパッチされた Thunk によって返された Promise には unwrap
プロパティがあり、これを呼び出すことで、fulfilled
アクションの payload
を抽出したり、rejected
アクションから rejectWithValue
によって作成された error
または利用可能な場合は payload
をスローしたりできます。
// in the component
const onClick = () => {
dispatch(fetchUserById(userId))
.unwrap()
.then((originalPromiseResult) => {
// handle result here
})
.catch((rejectedValueOrSerializedError) => {
// handle error here
})
}
または、async/await 構文を使用します。
// in the component
const onClick = async () => {
try {
const originalPromiseResult = await dispatch(fetchUserById(userId)).unwrap()
// handle result here
} catch (rejectedValueOrSerializedError) {
// handle error here
}
}
ほとんどの場合、添付の .unwrap()
プロパティを使用することをお勧めしますが、Redux Toolkit は同様の目的で使用できる unwrapResult
関数もエクスポートしています。
import { unwrapResult } from '@reduxjs/toolkit'
// in the component
const onClick = () => {
dispatch(fetchUserById(userId))
.then(unwrapResult)
.then((originalPromiseResult) => {
// handle result here
})
.catch((rejectedValueOrSerializedError) => {
// handle result here
})
}
または、async/await 構文を使用します。
import { unwrapResult } from '@reduxjs/toolkit'
// in the component
const onClick = async () => {
try {
const resultAction = await dispatch(fetchUserById(userId))
const originalPromiseResult = unwrapResult(resultAction)
// handle result here
} catch (rejectedValueOrSerializedError) {
// handle error here
}
}
ディスパッチ後のエラーの確認
これは、**Thunk での失敗したリクエストまたはエラーは、**決して**_拒否された_ Promise を返さない**ことを意味することに注意してください。この時点では、失敗は処理されない例外ではなく、処理されたエラーであると想定しています。これは、dispatch
の結果を使用しないユーザーに対して、キャッチされない Promise の拒否を防ぎたいという事実によるものです。
コンポーネントがリクエストが失敗したかどうかを知る必要がある場合は、.unwrap
または unwrapResult
を使用し、再スローされたエラーを適切に処理します。
Thunk エラーの処理
payloadCreator
が拒否された Promise(async
関数でスローされたエラーなど)を返すと、Thunk は action.error
としてエラーの自動的にシリアル化されたバージョンを含む rejected
アクションをディスパッチします。ただし、シリアル化可能性を確保するために、SerializedError
インターフェースと一致しないものはすべて削除されています。
export interface SerializedError {
name?: string
message?: string
stack?: string
code?: string
}
rejected
アクションの内容をカスタマイズする必要がある場合は、エラーを自分でキャッチし、thunkAPI.rejectWithValue
ユーティリティを使用して新しい値を**返す**必要があります。 return rejectWithValue(errorPayload)
を実行すると、rejected
アクションはその値を action.payload
として使用します。
API レスポンスが「成功」したが、Reducer が知っておくべき何らかの追加のエラー詳細が含まれている場合にも、rejectWithValue
アプローチを使用する必要があります。これは、API からフィールドレベルの検証エラーが予想される場合に特に一般的です。
const updateUser = createAsyncThunk(
'users/update',
async (userData, { rejectWithValue }) => {
const { id, ...fields } = userData
try {
const response = await userAPI.updateById(id, fields)
return response.data.user
} catch (err) {
// Use `err.response.data` as `action.payload` for a `rejected` action,
// by explicitly returning it using the `rejectWithValue()` utility
return rejectWithValue(err.response.data)
}
},
)
キャンセル
実行前のキャンセル
ペイロードクリエーターが呼び出される前に Thunk をキャンセルする必要がある場合は、ペイロードクリエーターの後にオプションとして condition
コールバックを提供できます。コールバックは Thunk 引数と {getState, extra}
をパラメーターとするオブジェクトを受け取り、それらを使用して続行するかどうかを決定します。実行をキャンセルする必要がある場合、condition
コールバックはリテラル false
値、または false
に解決される Promise を返す必要があります。Promise が返された場合、Thunk は pending
アクションをディスパッチする前に Promise が完了するのを待ちます。そうでない場合は、同期的にディスパッチを続行します。
const fetchUserById = createAsyncThunk(
'users/fetchByIdStatus',
async (userId: number, thunkAPI) => {
const response = await userAPI.fetchById(userId)
return response.data
},
{
condition: (userId, { getState, extra }) => {
const { users } = getState()
const fetchStatus = users.requests[userId]
if (fetchStatus === 'fulfilled' || fetchStatus === 'loading') {
// Already fetched or in progress, don't need to re-fetch
return false
}
},
},
)
condition()
が false
を返した場合、デフォルトの動作ではアクションはまったくディスパッチされません。Thunk がキャンセルされたときに「rejected」アクションをディスパッチする必要がある場合は、{condition, dispatchConditionRejection: true}
を渡します。
実行中のキャンセル
実行中の Thunk が完了する前にキャンセルする場合は、dispatch(fetchUserById(userId))
によって返された Promise の abort
メソッドを使用できます。
実際の例は次のようになります。
// file: store.ts noEmit
import { configureStore } from '@reduxjs/toolkit'
import type { Reducer } from '@reduxjs/toolkit'
import { useDispatch } from 'react-redux'
declare const reducer: Reducer<{}>
const store = configureStore({ reducer })
export const useAppDispatch = () => useDispatch<typeof store.dispatch>()
// file: slice.ts noEmit
import { createAsyncThunk } from '@reduxjs/toolkit'
export const fetchUserById = createAsyncThunk(
'fetchUserById',
(userId: string) => {
/* ... */
},
)
// file: MyComponent.ts
import { fetchUserById } from './slice'
import { useAppDispatch } from './store'
import React from 'react'
function MyComponent(props: { userId: string }) {
const dispatch = useAppDispatch()
React.useEffect(() => {
// Dispatching the thunk returns a promise
const promise = dispatch(fetchUserById(props.userId))
return () => {
// `createAsyncThunk` attaches an `abort()` method to the promise
promise.abort()
}
}, [props.userId])
}
Thunk がこの方法でキャンセルされた後、error
プロパティに AbortError
を持つ "thunkName/rejected"
アクションがディスパッチ(および返却)されます。Thunk はそれ以上のアクションをディスパッチしません。
さらに、payloadCreator
は thunkAPI.signal
経由で渡される AbortSignal
を使用して、コストのかかる非同期アクションを実際にキャンセルできます。
最新のブラウザの fetch
api には、すでに AbortSignal
のサポートが組み込まれています。
import { createAsyncThunk } from '@reduxjs/toolkit'
const fetchUserById = createAsyncThunk(
'users/fetchById',
async (userId: string, thunkAPI) => {
const response = await fetch(`https://reqres.in/api/users/${userId}`, {
signal: thunkAPI.signal,
})
return await response.json()
},
)
キャンセルの状態の確認
シグナル値の読み取り
signal.aborted
プロパティを使用して、Thunk が中止されたかどうかを定期的に確認し、その場合はコストのかかる長時間実行の作業を停止できます。
import { createAsyncThunk } from '@reduxjs/toolkit'
const readStream = createAsyncThunk(
'readStream',
async (stream: ReadableStream, { signal }) => {
const reader = stream.getReader()
let done = false
let result = ''
while (!done) {
if (signal.aborted) {
throw new Error('stop the work, this has been aborted!')
}
const read = await reader.read()
result += read.value
done = read.done
}
return result
},
)
中止イベントのリッスン
また、signal.addEventListener('abort', callback)
を呼び出して、promise.abort()
が呼び出されたときに Thunk 内のロジックに通知させることもできます。これは、たとえば axios CancelToken
と組み合わせて使用できます。
import { createAsyncThunk } from '@reduxjs/toolkit'
import axios from 'axios'
const fetchUserById = createAsyncThunk(
'users/fetchById',
async (userId: string, { signal }) => {
const source = axios.CancelToken.source()
signal.addEventListener('abort', () => {
source.cancel()
})
const response = await axios.get(`https://reqres.in/api/users/${userId}`, {
cancelToken: source.token,
})
return response.data
},
)
Promise の拒否がエラーまたはキャンセルによるものだったかどうかの確認
Thunk キャンセルに関する動作を調査するには、ディスパッチされたアクションの meta
オブジェクトのさまざまなプロパティを調べることができます。Thunk がキャンセルされた場合、Promise の結果は rejected
アクションになります(そのアクションが実際にストアにディスパッチされたかどうかに関係なく)。
- 実行前にキャンセルされた場合、
meta.condition
は true になります。 - 実行中に中止された場合、
meta.aborted
は true になります。 - これらのどちらも true でない場合、Thunk はキャンセルされておらず、Promise の拒否または
rejectWithValue
によって単に拒否されました。 - Thunk が拒否されなかった場合、
meta.aborted
とmeta.condition
はどちらもundefined
になります。
そのため、Thunk が実行前にキャンセルされたことをテストする場合、次のようにすることができます。
import { createAsyncThunk } from '@reduxjs/toolkit'
test('this thunk should always be skipped', async () => {
const thunk = createAsyncThunk(
'users/fetchById',
async () => throw new Error('This promise should never be entered'),
{
condition: () => false,
}
)
const result = await thunk()(dispatch, getState, null)
expect(result.meta.condition).toBe(true)
expect(result.meta.aborted).toBe(false)
})
例
- 読み込み状態を伴い、一度に1つのリクエストのみで、IDでユーザーをリクエストする
import { createAsyncThunk, createSlice } from '@reduxjs/toolkit'
import { userAPI, User } from './userAPI'
const fetchUserById = createAsyncThunk<
User,
string,
{
state: { users: { loading: string; currentRequestId: string } }
}
>('users/fetchByIdStatus', async (userId: string, { getState, requestId }) => {
const { currentRequestId, loading } = getState().users
if (loading !== 'pending' || requestId !== currentRequestId) {
return
}
const response = await userAPI.fetchById(userId)
return response.data
})
const usersSlice = createSlice({
name: 'users',
initialState: {
entities: [],
loading: 'idle',
currentRequestId: undefined,
error: null,
},
reducers: {},
extraReducers: (builder) => {
builder
.addCase(fetchUserById.pending, (state, action) => {
if (state.loading === 'idle') {
state.loading = 'pending'
state.currentRequestId = action.meta.requestId
}
})
.addCase(fetchUserById.fulfilled, (state, action) => {
const { requestId } = action.meta
if (
state.loading === 'pending' &&
state.currentRequestId === requestId
) {
state.loading = 'idle'
state.entities.push(action.payload)
state.currentRequestId = undefined
}
})
.addCase(fetchUserById.rejected, (state, action) => {
const { requestId } = action.meta
if (
state.loading === 'pending' &&
state.currentRequestId === requestId
) {
state.loading = 'idle'
state.error = action.error
state.currentRequestId = undefined
}
})
},
})
const UsersComponent = () => {
const { entities, loading, error } = useSelector((state) => state.users)
const dispatch = useDispatch()
const fetchOneUser = async (userId) => {
try {
const user = await dispatch(fetchUserById(userId)).unwrap()
showToast('success', `Fetched ${user.name}`)
} catch (err) {
showToast('error', `Fetch failed: ${err.message}`)
}
}
// render UI here
}
rejectWithValue を使用して、コンポーネントでカスタムの拒否されたペイロードにアクセスする
注:これは、userAPI が検証固有のエラーのみをスローすると仮定した、人為的な例です。
// file: store.ts noEmit
import { configureStore } from '@reduxjs/toolkit'
import type { Reducer } from '@reduxjs/toolkit'
import { useDispatch } from 'react-redux'
import usersReducer from './user/slice'
const store = configureStore({ reducer: { users: usersReducer } })
export const useAppDispatch = () => useDispatch<typeof store.dispatch>()
export type RootState = ReturnType<typeof store.getState>
// file: user/userAPI.ts noEmit
export declare const userAPI: {
updateById<Response>(id: string, fields: {}): { data: Response }
}
// file: user/slice.ts
import { createAsyncThunk, createSlice } from '@reduxjs/toolkit'
import { userAPI } from './userAPI'
import type { AxiosError } from 'axios'
// Sample types that will be used
export interface User {
id: string
first_name: string
last_name: string
email: string
}
interface ValidationErrors {
errorMessage: string
field_errors: Record<string, string>
}
interface UpdateUserResponse {
user: User
success: boolean
}
export const updateUser = createAsyncThunk<
User,
{ id: string } & Partial<User>,
{
rejectValue: ValidationErrors
}
>('users/update', async (userData, { rejectWithValue }) => {
try {
const { id, ...fields } = userData
const response = await userAPI.updateById<UpdateUserResponse>(id, fields)
return response.data.user
} catch (err) {
let error: AxiosError<ValidationErrors> = err // cast the error for access
if (!error.response) {
throw err
}
// We got validation errors, let's return those so we can reference in our component and set form errors
return rejectWithValue(error.response.data)
}
})
interface UsersState {
error: string | null | undefined
entities: Record<string, User>
}
const initialState = {
entities: {},
error: null,
} satisfies UsersState as UsersState
const usersSlice = createSlice({
name: 'users',
initialState,
reducers: {},
extraReducers: (builder) => {
// The `builder` callback form is used here because it provides correctly typed reducers from the action creators
builder.addCase(updateUser.fulfilled, (state, { payload }) => {
state.entities[payload.id] = payload
})
builder.addCase(updateUser.rejected, (state, action) => {
if (action.payload) {
// Being that we passed in ValidationErrors to rejectType in `createAsyncThunk`, the payload will be available here.
state.error = action.payload.errorMessage
} else {
state.error = action.error.message
}
})
},
})
export default usersSlice.reducer
// file: externalModules.d.ts noEmit
declare module 'some-toast-library' {
export function showToast(type: string, message: string)
}
// file: user/UsersComponent.ts
import React from 'react'
import { useAppDispatch } from '../store'
import type { RootState } from '../store'
import { useSelector } from 'react-redux'
import { updateUser } from './slice'
import type { User } from './slice'
import type { FormikHelpers } from 'formik'
import { showToast } from 'some-toast-library'
interface FormValues extends Omit<User, 'id'> {}
const UsersComponent = (props: { id: string }) => {
const { entities, error } = useSelector((state: RootState) => state.users)
const dispatch = useAppDispatch()
// This is an example of an onSubmit handler using Formik meant to demonstrate accessing the payload of the rejected action
const handleUpdateUser = async (
values: FormValues,
formikHelpers: FormikHelpers<FormValues>,
) => {
const resultAction = await dispatch(updateUser({ id: props.id, ...values }))
if (updateUser.fulfilled.match(resultAction)) {
// user will have a type signature of User as we passed that as the Returned parameter in createAsyncThunk
const user = resultAction.payload
showToast('success', `Updated ${user.first_name} ${user.last_name}`)
} else {
if (resultAction.payload) {
// Being that we passed in ValidationErrors to rejectType in `createAsyncThunk`, those types will be available here.
formikHelpers.setErrors(resultAction.payload.field_errors)
} else {
showToast('error', `Update failed: ${resultAction.error}`)
}
}
}
// render UI here
}