When trying to add product to user favorites I am getting this error. Although the state of redux is getting updated properly.
I have used Redux Thunk for Authentication, but I was trying to avoid adding to many extra reducers as the add a lot of code just for one function; and start using productService.js file.
userSlice.js
import {
createAction,
createSlice,
} from "@reduxjs/toolkit";
import {
loginUser,
registerUser,
} from "../actions/authActions";
import { getWithExpiry } from "../../utils/setStorage";
export const addFavoritesError = createAction(
"addFavoritesError"
);
const savedUserInfo = getWithExpiry("userInfo")
? getWithExpiry("userInfo")
: null;
const savedToken = getWithExpiry("userToken")
? getWithExpiry("userToken")
: null;
const initialValue = {
isLoading: false,
userInfo: savedUserInfo,
userToken: savedToken,
error: null,
success: false,
};
const authSlice = createSlice({
name: "auth",
initialState: initialValue,
reducers: {
login: (state) => {
loginUser().then((res) => {
// history.pushState('/');
});
},
logOut: (state) => {
return {
...state.initialState,
isLoading: false,
userInfo: null,
userToken: null,
error: null,
success: false,
};
},
signUp: (state) => {
registerUser().then((res) => {
// history.pushState('/');
});
},
addFavorites: (state, action) => {
return {
...state,
userInfo: {
...state.userInfo,
favorites: [...action.payload],
},
};
},
},
extraReducers: {
// 1 login user
[loginUser.pending]: (state) => {
state.isLoading = true;
state.error = null;
},
[loginUser.fulfilled]: (state, { payload }) => {
state.isLoading = false;
state.success = true;
state.userInfo = payload.data.user;
state.userToken = payload.token;
},
[loginUser.rejected]: (state, { payload }) => {
state.isLoading = false;
state.error = payload;
},
// 2 register user
[registerUser.pending]: (state) => {
state.isLoading = true;
state.error = null;
},
[registerUser.fulfilled]: (state, { payload }) => {
state.isLoading = false;
state.success = true;
state.userInfo = payload;
state.userToken = payload.token;
},
[registerUser.rejected]: (state, { payload }) => {
state.isLoading = false;
state.error = payload;
},
},
});
export const { logOut, addFavorites } = authSlice.actions;
export default authSlice.reducer
userService.js
import apiActions from "../../utils/api";
import { addFavorites, addFavoritesError } from "../slices/userSlice";
export const AddProdToFavorites = async (dispatch, user_id, prod_id) => {
try {
// api call
const data = await apiActions.addProdToUserFav(user_id, prod_id);
dispatch(addFavorites(data));
} catch {
dispatch(addFavoritesError());
}
};`
ProductCard.js
import React, { useCallback, useState } from "react";
import { useDispatch} from "react-redux";
import { AddProdToFavorites} from "../../../store/services/userService";
function DestinationsDisplayCard(props) {
const { userToken, name, price} = props;
const [favored, setFavored] = useState(false);
const dispatch = useDispatch();
const addItemtoFav = useCallback(() => {
setFavored((currrent) => !currrent);
dispatch(AddProdToFavorites(dispatch, userToken, prodId));
}, [favored]);
const removeItemFromFav = useCallback(() => {}, [favored]);
return (
<div className="productCard">
<p>{name}</p>
<p>${price}</p>
{userToken ? (
<button
onClick={favored ? removeItemFromFav : addItemtoFav}
>
</button>
) : null}
</div>
)
}
api.js
import axios from "axios";
const baseURL = "<<the url for api>>";
const apiActions = {
addProdToUserFav: async (user_id, prod_id) => {
const res = await axios.post(
`${baseURL}/v1/user/addFavorite`,
{
userId: user_id,
prodId: prod_id,
}
);
return res.data.data.user.favorites;
},
// this endpoint returns a list of object [{product2}{product2}] (with name, price, discount, etc)
}
I expected that state.userInfo.favorites to update, which it does, but I am getting this error.
Uncaught Error: Actions must be plain objects. Use custom middleware for async actions.
VM41:1 Uncaught Error: Actions must be plain objects. Use custom middleware for async actions.
at Object.performAction (<anonymous>:1:41504)
at k (<anonymous>:3:1392)
at s (<anonymous>:3:5102)
at serializableStateInvariantMiddleware.ts:210:1
at index.js:20:1
at Object.dispatch (immutableStateInvariantMiddleware.ts:264:1)
at dispatch (<anonymous>:6:7391)
at DestinationsDisplay.card.jsx:23:1
at HTMLUnknownElement.callCallback (react-dom.development.js:4164:1)
at Object.invokeGuardedCallbackDev (react-dom.development.js:4213:1)
2
Answers
My prior comment implied that Redux Thunk had to be explicitly introduced, which was wrong. If you’re using Redux Toolkit and still getting the "Actions must be plain objects" error, there might be another problem in your code. Please double-check that you are doing the actions correctly. Make sure your Redux slice file (
userSlice.js
) calls the Redux Toolkit’screateSlice
method. Make sure you’re dispatching the actions directly in your async thunk action (AddProdToFavorites
). Here’s how yourAddProdToFavorites
async thunk should appear within your Redux Toolkit slice (userSlice.js
):Please ensure that your Redux slice, actions, and reducers are properly configured.
You have a few issues with the way you are trying to use regular reducer functions to handle asynchronous side-effects, and dispatching objects to the store that are neither action objects nor asynchronous action creators, e.g. Thunks.
AddProdToFavorites
is a regular function and should be rewritten to use Redux-Toolkit’screateAsyncThunk
.The suggested/recommended patter is to return directly the fetched data or rejected Promise with error value, and use the state slice’s
extraReducers
to handle the state updates. Remember that with RTK you can write mutable state updates, you don’t need to shallow copy all the state.or if you are really wanting to write and keep the extra actions, use the second argument to the thunk to access the
dispatch
function and dispatch your other actions to the store. Handle these actions in the regularreducers
with the same mutable state update.Dispatching
AddProdToFavorites
action:In
authSlice
thelogin
andsignup
actions/reducers are invalid. Reducer functions are to be pure, synchronous functions. These two actions should also be Thunks, and the UI should handle chaining orawait
-ing them to settle in order to issue the additional side-effect, e.g. the navigation. It looks likeloginUser
andregisterUser
are already RTK Thunks, so remove thelogin
andsignup
actions/reducers from theauthSlice
and handle the asynchronous logic in the calling function.Example:
Unwrap the returned resolved Promise to handle the fulfilled/rejected status.
or