😲CreateAsyncThunk가 필요했던 이유
toastify 라이브러리를 분석해보면 더 좋았겠고.. 실제로 toastify 라이브러리는
코드를 분석해본 사람들도 꽤 많아서 참고해볼 수 있었지만
우선 조금 이상하게라도 최소 기능은 할 수 있는 Toast를 만들어보고 싶었습니다.
다만 다른 사람들의 방식을 참고해보았을때는 이해하기가 조금 힘들었습니다.
따라서 제 방식대로 바퀴의 재발명을 시도해보았는데
제 시행착오 과정은 다음과 같습니다.
1. 우선 useState를 통해 state가 true이면 토스트를 보여줍니다.
-> 문제 : 토스트를 여러개 보여주고 싶으면 어떻게 해야할까요?
2. 토스트를 여러개 보여주기 위해 각각의 카드에게 개별적인 이벤트 핸들러를 달아주고
이벤트 핸들러가 눌렸을때 toast Conatiner에게 몇개의 토스트가 띄워져야하는지를 알려줍니다.
-> 문제 : 특정 데이터에 따라 토스트 팝업이 다르게 띄워져야한다면 어떻게 해야할까요?
-> 문제 : 각각의 카드에게 개별적인 핸들러를 달아주려면 컴포넌트 트리의 아래쪽으로 토스트 컴포넌트가 내려가게되는데 그럼 토스트를 어디에 렌더링 시켜줘야할까요?
3. 토스트를 컴포넌트 트리에서 자유롭게 해주기 위해 createPortal을 적용해야한다는 결론을 얻었습니다.
-> createPortal을 통해 app.tsx에 토스트를 children으로 갖는 토스트컨테이너를 항상 렌더시키긴하는데
그럼 어떻게 toast container에게 하위 컴포넌트의 상태를 전달시킬 수 있을까요?
4. 이정도 쯤 오니 슬슬 useState로는 엄두가 나지 않기 시작합니다.
아무래도 전역 상태로 관리해야할 듯 합니다.
그리고 여러개의 토스트를 관리할 방법도 고려해야합니다.
5. toast container에서 useEffect를 통해 3초 뒤에 토스트를 지우는 로직을 작성했습니다.
지워져야할 토스트들을 모아둔 배열을 하나 만들고 그 배열을 관리하도록하면 되지않을까요?
여러개의 데이터를 지워야하는 경우에는 하나하나 디스패치를 보내고 하나하나 반복을 도는동안 계속 리렌더링이 되는 문제가 있었습니다.
🧅어떻게하지..?
생각해보는데 모든 토스트는 생성된 시점부터 일정 시간동안만 유지되고 사라지면 됩니다.
즉 어차피 일정시간 유지되고 사라질 토스트들은 생성되는 시점에 사라지는 시점도 결정됩니다.
이런 로직은 setTimeOut을 활용하면 쉽게 구현할 수 있습니다.
하지만 리덕스의 리듀서는 순수함수여야합니다.
따라서 setTimeout과 같은 로직을 가진 함수들은 리덕스의 리듀서에 들어갈 수 없습니다.
그렇기 때문에 비동기 로직을 수행할 수 있도록 redux toolkit이 제공하는 createAsyncThunk 함수를 떠올렸습니다.
그럼 여기까지 도달하면서 얻은 결론을 나열하면 다음과 같습니다.
1. ReduxToolkit을 이용해 토스트 로직 자체를 slice화해서 관리한다.
2. ToastContainer 컴포넌트는 createPortal을 통해 돔에 위치시켜야 한다.
3. ReduxToolkit으로 관리하는 상태를 ToastContainer는 그저 렌더링만 시켜주면 된다.
그림으로 그려보면 대충 이런 로직이라고 이해할 수 있습니다.
card 컴포넌트에서 redux를 이용해 dispatch를 날려서 상태를 업데이트 시키면
redux의 toast 상태를 구독하는 toast 컴포넌트는
dispatch에 맞추어 화면에 렌더되는 toast를 최신화해주는 것입니다.
😅 Toast 하기전에 createAsyncThunk 로 deep dive
우선 createAsyncThunk는 실제로 사용해본적은 없기 때문에 공식문서를 먼저 참고했습니다.
https://redux-toolkit.js.org/usage/usage-with-typescript#createasyncthunk
먼저 공식문서를 차근차근 읽어봅시다.
타입스크립트를 사용할 것이니 타입에 대한 정보를 우선 흝어보겠습니다.
interface MyData {
// ...
}
const fetchUserById = createAsyncThunk(
'users/fetchById',
// Declare the type your function argument here:
async (userId: number) => {
const response = await fetch(`https://reqres.in/api/users/${userId}`)
// Inferred return type: Promise<MyData>
return (await response.json()) as MyData
}
)
// the parameter of `fetchUserById` is automatically inferred to `number` here
// and dispatching the resulting thunkAction will return a Promise of a correctly
// typed "fulfilled" or "rejected" action.
const lastReturnedAction = await store.dispatch(fetchUserById(3))
공식문서의 예제로 제공되는 코드인데 이 코드만 보았을때는 정의하는 것 자체는 어렵지 않아보입니다.
createAsyncThunk는 두가지 인수를 받는데 첫번째에는 어떤.. 식별을 해줄 수 있는 키값이 들어가고
두번째에는 비동기로직을 처리하는 함수가 들어가는 느낌의 코드로 보이네요
또한 일반적인 사용 사례에서 createAsyncThunk 호출 자체에 대한 유형을 명시적으로 선언할 필요는 없다고 합니다.
좋아요 타입은 나름대로 알아서 처리를 해주는 모양이네요
다음은 api reference를 참고해보겠습니다.
https://redux-toolkit.js.org/api/createAsyncThunk
위 문서에서는 createAsyncThunk를 다음과 같이 정의합니다.
Redux Action Type String과 Promise를 반환해야하는 콜백 함수를 인자로 받는 함수
아까 타입을 볼 때 잠깐 봤던 예제가 떠오르는 정의네요
마치 파일경로같이 생긴 첫번째 인수를 기반으로 Promise LifeCycle Action Type을 생성하고
Promise Callback을 실행하며 반환된 Promise를 기반으로
LifeCycle Action을 dispatch할 Thunk Action Creator를 반환해준다.
라고 합니다.
라이프 사이클 액션 타입과 Thunk Action Creator는 이후에 생김새를 보면 쉽게 이해할 수 있으니
일단 넘어가보도록 하겠습니다.
이러한 createAsyncThunk는 async Request lifecycles 들을 처리하기 위한
표준적이고 권장되는 접근 방식을 추상화해주는 역할을 합니다.
그러니까.. 매우 귀찮은 일을 하기 쉽게 간단하게 만들어준다는 얘기네요!
또한 이 createAsyncThunk 함수는 어떤 데이터를 가져올지, 로딩 상태를 어떻게 추적할지, 반환된 데이터를 어떻게 처리해야하는지와 같은 정보를 알지 못하기 때문에 dispatch 함수를 생성하지 않습니다.
따라서 앱에 적합한 로딩 상태 및 처리 로직을 사용해서 이러한 작업을 처리하는 자체적인 dispatch 로직을 작성해야한다고 합니다.
여기까지 읽어보면 이거 완전 서버에 데이터 요청해서 받아오는 일을 하라고 만든거 아닌가? 싶지만
그런 일을 RTK Query가 제공해주기 때문에 그쪽을 사용하는 편이 더 좋겠습니다.
다만 저는 서버 데이터를 불러오는게 아니라 setTimeout을 사용하고 싶을 뿐이니 이쪽 솔루션을 선택해도 좋겠네요
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: []
loading: 'idle' | 'pending' | 'succeeded' | 'failed'
}
const initialState = {
entities: [],
loading: 'idle',
} 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는 비동기 상태에 대한 액션을 제공해줍니다.
이거 비동기 상태에 따른 액션 하나하나 다 만들었던 적이 있는데
createAsyncThunk는 이걸 바로 제공해주니까 정말 편하네요
예컨대 유형인수가 users/reqeustStatus인경우엔 이런 액션들이 생성됩니다.
pending | users/requestStatus/pending |
fulfilled | users/requestStatus/fulfilled |
rejected | users/requestStatus/rejected |
createAsyncThunk 사용법 알아보기
payloadCreator
2번째 인수에 들어가는 함수를 payloadCreator라고 부르나 보네요!
payloadCreator는 async logic의 결과가 포함된 promise를 반환해야하는 콜백 함수입니다.
그런데 값을 동기적으로 반환할 수도 있어요
또한 오류가 있는 경우에는 reject된 프로미스를 반환하거나
그렇지 않다면 thunkAPI.RejectWithValue 인수가 있는 해결된 프로미스를 반환해야한다고 합니다.
payloadCreator에는ajax calls, 여러 ajax calls을 하나로 합치는 호출, RN의 AsyncStorage와의 상호작용 등이 포함될 수 있다고하네요
이러한 payloadCreator는 두가지 인자를 받습니다.
위 예제에서는 바로 이부분입니다.
async (userId: number, thunkAPI) => {
const response = await userAPI.fetchById(userId)
return response.data
}
arg, thunkAPI는 createAsyncThunk의 두번째 인자인 콜백함수에 들어가는 인자들입니다.
arg
arg는 single Value여야합니다. 이 인자는 thunk action creator가 전달받은 첫번째 매개변수를 포함하게됩니다.
나중에 로직을 완성하고 나면 외부에서 thunk 함수를 호출해서 사용할건데
그 thunk 함수의 인자에 넣어준 값이 들어온다는 설명입니다.
만약 여러개의 값을 전달해야하는 경우가 있을 수 있는데 그러한 경우에는 객체 형태로 감싸서 보내주면 됩니다.
dispatch(fetchUsers({status: 'active', sortBy: 'name'}))
이건 완성한 createAsyncThunk 로직을 사용하는 코드입니다.
dispatch 함수를 통해 asyncThunk 함수를 감싸주고 asyncThunk함수 내부에는
여러가지 값을 single Value로 전달해주어야하니 객체의 프로퍼티형태로 보내주는것입니다.
ThunkAPI
thunkAPI에는 일반적으로 redux thunk 함수에 전달되는 모든 매개변수와 추가 옵션이 포함된 객체가 제공됩니다.
제공되는 기능들은 다음과 같은데 정말 많은 기능이 제공되는걸 알 수 있습니다.
dispatch | 이건 익숙합니다. action을 dispatch해주는 dispatch함수입니다. asyncThunk 내부에서 redux의 reducer를 사용해야할 때 유용합니다. |
getState | redux store의 getState와 같은 일을 수행하는 메서드입니다. |
extra | 설정을 해주면 thunk middleware에 추가적으로 제공되는 인자라고 하는데 이건 잘 모르겠습니다. |
requestId | 유니크한 string ID value가 담겨있습니다. 자동으로 생성되며 request sequesnce를 identify 해주는 일을 합니다. |
signal | AbortController.signal 객체를 제공해줍니다. 이것은 앱 로직의 다른 부분에서 이 요청을 취소가 필요한 것으로 표시했는지 확인하는데 사용할 수 있습니다. |
rejectWithValue(value, [meta]) | 아까 잠깐 보았던 rejectWithValue입니다. action creator에서 return 혹은 throw 하여 정의된 payload 및 meta가 포함된 reject 밸류를 반환할 수 있는 유틸함수입니다. 이 함수는 사용자가 지정한 값을 전달하여 거부된 액션의 페이로드에 반환합니다. |
fulfillWithValue(value,meta) | 이것도 유틸함수인데 action creator에서 값을 return 할 수 있는 기능을 가집니다. |
Options
추가로 옵션 객체도 받을 수 있지만
이미 충분히 많은 정보를 보았으니 이것에 대해 관심이 있다면 공식문서를 참고해보세요..
Return Value
createAsyncThunk는thunk action creator를 반환합니다.
이 thunk action creator 함수에는 pending, fulfilled, rejected에 대한
일반 액션 생성자가 중첩된 형태로 제공되어요
따라서 위에서 쭉 보고왔던 예제를 사용하게되면 createAsyncThunk 함수는
4개의 함수를 생성해줍니다.
fetchUserById | 사용자가 작성한 비동기 페이로드 콜백을 시작하는 action 생성자 함수입니다. |
fetchUserById.pending | pending 액션을 디스패치합니다. |
fetchUserById.fulfilled | fulfilled 액션을 디스패치합니다. |
fetchUserById.rejected | rejected 액션을 디스패치합니다. |
자바스크립트는 함수를 일급객체로 사용하는 언어이기 때문에 가능한 문법인 것 같습니다.
이제 실제 사용례로 넘어가보겠습니다.
Handling Thunk Results
thunk는 놀랍게도 디스패치 될 때 값을 반환할 수가 있습니다.
thunk는 promise를 반환한다고 했던 것을 기반으로 프로미스 후속처리 메서드를 붙여줄 수가 있네요
const onClick = () => {
dispatch(fetchUserById(userId)).then(() => {
// do additional work
})
}
Canceling Before Execution
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
}
},
}
)
createAyncThunk는 2가지의 필수인자를 받지만 3번째 옵션 인자도 받을 수 있습니다.
이 옵션인자에 condition 키를 전달하는 것으로 페이로드 생성자가 호출되기 전에 썽크를 취소할 수도 있습니다.
condition에 들어가는 함수는 불리언 값을 반환하는 함수여야하네요
그 외에도 다양한 기능과 예제가 공식문서에서 제공되니 더 다양한 옵션이 필요하신 경우
공식 문서를 체킹해보시는것을 추천드리겠습니다.
🙀createAsyncThunk를 이용해 일정시간 후 사라지는 토스트팝업 만들기
정말 힘들게 createAsyncThunk를 쭉 흝어보았지만
이제 배운것을 토대로 실제 로직을 작성해보아야할것입니다.
제가 만들고자 하는 기능은 다음과 같습니다.
1. toast는 여러개가 창에 띄워질 수 있어야한다.
2. toast는 먼저 생성된 toast가 먼저 소멸해야한다.
3. toast는 사용자가 원한다면 특정 toast만 지울 수 있어야한다.
4. toast는 전달받은 내용에 따라 다른 내용을 보여줄 수 있어야한다.
따라서 이 조건을 만족하는 slice를 먼저 생성해주겠습니다.
리덕스툴킷과 타입스크립트를 이용합니다.
이용하는 폴더 구조는 다음과 같습니다.
src
│ ├── modules
│ │ ├── toastSlice.ts
│ │ ├── index.ts
import { createSlice, PayloadAction, createAsyncThunk } from '@reduxjs/toolkit';
export interface ToastMessageInterface {
id: number;
content: string;
isVisible: boolean;
bookmark: boolean;
}
export interface ToastInterface {
messages: ToastMessageInterface[];
}
const initialState: ToastInterface = {
messages: [],
};
const toastSlice = createSlice({
name: 'toastSlice',
initialState,
reducers: {
showToast: (state, action: PayloadAction<ToastMessageInterface>) => {
state.messages.push({
id: action.payload.id,
content: action.payload.content,
isVisible: true,
bookmark: action.payload.bookmark,
});
},
hideToast: (state, action: PayloadAction<number>) => {
const msgIdx = state.messages.findIndex(
(msg) => msg.id === action.payload,
);
state.messages.splice(msgIdx, 1);
},
shiftToast: (state) => {
state.messages.shift();
},
},
});
export const showToastAsync = createAsyncThunk<void, ToastMessageInterface>(
'toast/showToastAsync',
async (message, { dispatch }) => {
dispatch(toastSlice.actions.showToast(message));
await new Promise((resolve) => setTimeout(resolve, 2000));
dispatch(toastSlice.actions.shiftToast());
},
);
export const { showToast, hideToast } = toastSlice.actions;
export default toastSlice;
선입선출 구조이기 때문에 shift 보다는 큐를 구현해서 사용하면 더 좋을 것 같았지만
토스트가 엄청나게 많이 생성되지는 않을 것이기때문에 우선은 shift로 구현했습니다.
코드를 하나하나 흝어보면 간단하게 구성되어있음을 알 수 있습니다.
위에서 아래로 코드를 읽어나가보도록하겠습니다.
import { createSlice, PayloadAction, createAsyncThunk } from '@reduxjs/toolkit';
export interface ToastMessageInterface {
id: number;
content: string;
isVisible: boolean;
bookmark: boolean;
}
export interface ToastInterface {
messages: ToastMessageInterface[];
}
const initialState: ToastInterface = {
messages: [],
};
필요한 함수,타입들을 불러온 뒤 ToastMessage의 형식을 규정하고 initialState의 형식을 규정하는 코드입니다.
state.messages를 통해 배열에 접근할 수 있을 것입니다.
객체로 감싸주는게 아니라 바로 배열을 붙여주어도 상관 없기는 합니다.
const toastSlice = createSlice({
name: 'toastSlice',
initialState,
reducers: {
showToast: (state, action: PayloadAction<ToastMessageInterface>) => {
state.messages.push({
id: action.payload.id,
content: action.payload.content,
isVisible: true,
bookmark: action.payload.bookmark,
});
},
hideToast: (state, action: PayloadAction<number>) => {
const msgIdx = state.messages.findIndex(
(msg) => msg.id === action.payload,
);
state.messages.splice(msgIdx, 1);
},
shiftToast: (state) => {
state.messages.shift();
},
},
});
slice를 정의합니다.
먼저 showToast는 앞서 정의한 Message 객체를 입력으로 받아서
입력에 따라 다른 토스트 팝업을 보여줄 수 있도록 구성합니다.
전 bookmark의 상태에 따라 보여질 토스트의 뷰를 다르게 설정해줘야했지만
id, content는 필요합니다.
코드를 뜯어보면서 느낀건데 isVisible은 필요가없네요.. 저건 뺍시다.
여러분은 이렇게 메시지를 정의해주면 될 것 같습니다.
export interface ToastMessageInterface {
id: number;
content: string;
}
만약 id를 slice 단에서 관리해줄것이라면 id조차도 필요없을 수 있겠네요
reducers: {
showToast: (state, action: PayloadAction<ToastMessageInterface>) => {
state.messages.push({
id: action.payload.id,
content: action.payload.content,
isVisible: true,
bookmark: action.payload.bookmark,
});
},
hideToast: (state, action: PayloadAction<number>) => {
const msgIdx = state.messages.findIndex(
(msg) => msg.id === action.payload,
);
state.messages.splice(msgIdx, 1);
},
shiftToast: (state) => {
state.messages.shift();
},
},
리듀서를 정의해줍니다.
제 생각에는 기본적인 기능만을 수행한다했을때 저 세가지 리듀서정도면 충분할 것 같습니다.
showToast는 입력받은 내용을 기반으로 토스트팝업을 구성합니다.
state로 만들어둔 배열에 우리의 토스트 팝업 객체를 넣어줍니다.
이 배열은 나중에 컴포넌트에서 map 배열 메서드를 통해 렌더시켜줄것입니다.
hideToast는 사용자가 지우고싶은 토스트를 클릭했을 때
아이디를 기반으로 눌린 토스트를 찾아 없애주는 로직을 수행합니다.
shiftToast는 생명주기가 다 된 토스트를 화면에서 보이지않게하는 역할을 수행합니다.
먼저 들어간 큐가 가장 먼저 나와야하니 shift를 통해 먼저 들어간 객체들을 가장 먼저 빼줍니다.
export const showToastAsync = createAsyncThunk<void, ToastMessageInterface>(
'toast/showToastAsync',
async (message, { dispatch }) => {
dispatch(toastSlice.actions.showToast(message));
await new Promise((resolve) => setTimeout(resolve, 2000));
dispatch(toastSlice.actions.shiftToast());
},
);
이제 createAsyncThunk함수를 정의해주겠습니다.
지금 작성할 코드는 아무것도 반환하지않기때문에 void를 주었지만
원래는 promise를 반환해야할것입니다.
두번째 제네릭은 비동기 썽크가 수신한 인수 유형을 나타냅니다.
즉 message 매개변수의 타입을 정해주는 부분입니다.
키값을 설정해주고 두번째 인수에서는 비동기로직 구현을 위해 async 함수를 사용해줍니다.
두번째 인수에 들어가는 함수의 두번째 인수에는 thunkAPI 객체를 받을 수 있다고 했습니다.
그 중 dispatch를 구조분해할당하여 가져옵니다.
dispatch를 통해 toastSlice.action.showToast 액션을 전송합니다.
그리고 2초를 기다린뒤 messages 배열에서 shift 해줍니다.
배열에서 빠지게되면 자연스럽게 화면에서도 사라지도록 로직을 구현할 것입니다.
export const { showToast, hideToast } = toastSlice.actions;
export default toastSlice;
만들어준 액션함수와 슬라이스를 내보내줍니다.
이제 슬라이스를 스토어에 넣어주도록 하겠습니다.
src/module/index.ts
import toastSlice from './toastSlice';
import { configureStore } from '@reduxjs/toolkit';
const store = configureStore({
reducer: {
toast: toastSlice.reducer,
}
});
export default store;
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;
리듀서로 넣어줍니다.
이제 이 store를 Provider의 prop으로 제공해줘야겠습니다.
src/main.tsx
import ReactDOM from 'react-dom/client';
import App from './App.tsx';
import './index.css';
import { Provider } from 'react-redux';
import store from './modules/index.ts';
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
<Provider store={store}>
<App />
</Provider>,
);
🥰이제 실제로 함수를 사용하자
이제 준비는 다되었으니 실제로 화면에 렌더링을 할 toastContainer를 만들어줘야하는데
토스트는 독립적인 영역에서 띄워져야할테니 먼저 createPortal을 이용해주겠습니다.
ToastConatiner.tsx
import { useSelector, useDispatch } from 'react-redux';
import { hideToast } from '../../modules/toastSlice';
import { RootState } from '../../modules';
import { createPortal } from 'react-dom';
import { TiDeleteOutline } from 'react-icons/ti';
const ToastContainer = () => {
const messages = useSelector((state: RootState) => state.toast.messages);
const dispatch = useDispatch();
const toastElement = document.getElementById('toast') as HTMLElement;
return (
<>
{createPortal(
messages.map((message) => (
<li
key={message.id}
className="fcc my-3 rounded-lg bg-red-500 px-6 py-5 text-white"
>
<p className=" mx-2">{message.content}</p>
<TiDeleteOutline
className=" h-[2rem] w-[2rem] cursor-pointer"
onClick={() => dispatch(hideToast(message.id))}
/>
</li>
)),
toastElement,
)}
</>
);
};
export default ToastContainer;
컴포넌트를 하나 만들어주었습니다. createPortal에 대한 사용법은 아래 링크에서 확인할 수 있습니다.
https://xionwcfm.tistory.com/316
useSelector를 이용해 messages 배열을 구독시키고
토스트팝업을 클릭하면 지워질 수 있도록 hideToast 액션함수도 불러왔습니다.
메시지 배열에 있는 요소들이 렌더링되는 것이니
메시지 배열에서 요소가 shift 되면 화면에 보이는 내용도 달라지게됩니다.
이 ToastConatiner는 상태를 구독하며 상태를 화면에 렌더링시켜주는 역할을 하는 컴포넌트입니다.
실제로 토스트가 추가되는 로직은 다른곳에서 수행해주어야합니다.
다만 그 이전에 ToastContainer 역시 화면에 보이게하려면 컴포넌트 트리에 있기는 해야하니
app.tsx에 ToastContainer를 넣어주겠습니다.
ProductImage.tsx
import createToastMessage from '../../utils/createToastMessage';
import { useDispatch } from 'react-redux';
import { showToastAsync } from '../../modules/toastSlice';
const ProductImage = ({ src, data }: ProductImageProps) => {
const dispatch = useDispatch();
return (
<div className={` relative mb-3 flex`}>
<button
className="absolute bottom-1 right-3 m-3 cursor-pointer"
onClick={() => {
dispatch(showToastAsync(createToastMessage(true)));
}}
>
</button>
</div>
);
};
실제로는 더 많은 코드가 있지만 편의상 asyncThunk와 관련없는 로직은 모두 제거했습니다.
createToastMessage는 아까 봤던 인터페이스에 맞는 내용을 생성해주는 함수입니다.
dispatch로 showToastAsync를 감싸서 호출해주면 끝입니다.
이제 버튼을 클릭하고 나면 토스트 창이 화면에 뜨게 될 것입니다.
간단한 시연화면을 준비했습니다.
css와 관한 설정들은 추가로 해주셔야 합니다
북마크 여러개가 잘 뜨고 순서대로 사라지는것을 볼 수 있습니다
레퍼런스
https://velog.io/@raejoonee/createAsyncThunk
createAsyncThunk의 실전 사용법에 대해 알기 쉽게 다루어주는 글입니다.
제 포스트에서 소개하지 않은 내용들이 많이 있으니 한번 읽어보세요!
'redux' 카테고리의 다른 글
RTK Query 데이터에 local Storage를 사용하는 방법 (0) | 2023.05.15 |
---|---|
RTK Query의 Middleware에 대하여 (0) | 2023.05.14 |
updateQueryData RTKquery로 클라이언트에서만 쿼리 데이터를 조작하고 싶다면 (1) | 2023.05.14 |
예제와 함께 TS ReduxToolkit Query Slow start (1) | 2023.05.01 |
Redux와 Redux-toolkit (0) | 2023.04.15 |