I'm new with typescript and I'd like to implement redux-toolkit for my app, I also want test using asynchronous call. it's actually work with my code but the problem is when I see the log, I see action name is written undefined, is this normal or is there anything I need to add? I believe the action name is not supposed to be undefined? or is there any best practice to call asynchronous call for typescript. I have been looking anywhere and found nothing
import React from "react";
import { useAppSelector, useAppThunkDispatch } from "../hooks/useRedux";
import { alertMessage } from "../store/post";
const List: React.FC = () => {
const { posts } = useAppSelector((state) => state.post);
const thunkDispatch = useAppThunkDispatch();
return (
<div style={{ marginTop: "20px" }}>
{posts.map((post) => {
const onAlert = () => {
thunkDispatch(alertMessage());
};
return (
<div
key={post.id}
style={{
marginBottom: "20px",
display: "flex",
justifyContent: "space-between",
alignItems: "center",
maxWidth: "300px",
}}
>
<div>
<div>{post.title}</div>
</div>
<div>
<button onClick={onAlert}>Alert Message</button>
</div>
</div>
);
})}
</div>
);
};
export default List;
// hooks/useRedux.ts
import { createAsyncThunk } from "@reduxjs/toolkit";
import { TypedUseSelectorHook, useDispatch, useSelector } from "react-redux";
import type { AppDispatch, AppThunkDispatch } from "../store/index";
import { RootState } from "../store/reducers";
export const useAppDispatch: () => AppDispatch = useDispatch;
export const useAppThunkDispatch: () => AppThunkDispatch = useDispatch;
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;
export const createAppAsyncThunk = createAsyncThunk.withTypes<{
state: RootState;
dispatch: AppDispatch;
rejectValue: string;
extra: { s: string; n: number };
}>();
import {
AnyAction,
configureStore,
Middleware,
MiddlewareArray,
} from "@reduxjs/toolkit";
import reducer, { RootState } from "./reducers";
import logger from "redux-logger";
import thunk, { ThunkDispatch } from "redux-thunk";
const customMiddleware: Middleware<{}, RootState> =
(store) => (next) => (action) => {
const state = store.getState();
next(action);
};
const store = configureStore({
reducer,
middleware: new MiddlewareArray()
.prepend(customMiddleware)
.concat(logger, thunk),
devTools: true,
});
export type AppDispatch = typeof store.dispatch;
export type AppThunkDispatch = ThunkDispatch<RootState, any, AnyAction>;
export default store;
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
import produce from "immer";
import { createAppAsyncThunk } from "../hooks/useRedux";
interface IPost {
userId: number;
id: number;
title: string;
body: string;
}
interface IPosts {
alertCount: number;
posts: IPost[];
}
const initialState: IPosts = {
alertCount: 0,
posts: [
{
id: 0,
userId: 0,
title: "this is title",
body: "this is body",
},
],
};
export const alertMessage = createAppAsyncThunk(
"post/alertMessage",
async (_, thunk) => {
return await callAlert();
}
);
const callAlert = () => {
return new Promise((resolve) => {
setTimeout(() => {
return resolve(alert("Hi"));
}, 2000);
});
};
const postSlice = createSlice({
name: "post",
initialState,
reducers: {
addPost: (
state,
action: PayloadAction<{ title: string; body: string }>
) => {
return produce(state, (draftState) => {
draftState.posts.push({
id: new Date().getTime(),
userId: new Date().getTime(),
...action.payload,
});
});
},
},
extraReducers: (builder) => {
builder.addCase(alertMessage.fulfilled, (state) =>
produce(state, (draftState) => {
draftState.alertCount = draftState.alertCount + 1;
})
);
},
});
export const { addPost } = postSlice.actions;
export default postSlice.reducer;
I think the action name shouldn't be "undefined" log example
If you put the logger before the thunk middleware, it will log those thunks - and since thunks are just functions, not actions, you will just log someFunction.type
- which is undefined
.
The logger belongs after the thunk middleware - and you should probably also be using getDefaultMiddleware
.