Updating state in Redux using Redux toolkit in NextJS based application - javascript

I am trying to make a cart based ecommerce website. I am using Redux and Redux Toolkit to simplify things. I am fairly new to this and my code is not working as expected.
I am trying to dispatch data to state but the state does not update. Redux Devtools show that data is being indeed dispatched to store, but the state remains the same.
In the action, I am console logging the state items and I think the problem lies here which I can't understand.
import { createSlice } from "#reduxjs/toolkit";
let initialState = [
{
name: "",
id: "",
quantity: "",
mode: "",
bookType: "",
},
];
const cartSlice = createSlice({
name: "cart",
initialState,
reducers: {
add: (state, action) => {
return state.map((item) => {
console.log(item);
if (item.id !== action.payload.id) {
return item;
}
return {
...item,
added: true,
};
});
},
remove: (state, action) => {
return state.map((item) => {
if (item.id !== action.payload.id) {
return item;
}
return {
...item,
added: false,
};
});
},
},
});
export const { add, remove } = cartSlice.actions;
export const getCart = (state) => state.cart;
export default cartSlice.reducer;
Here is console output:
This is how I am dispatching in my code:
function formSubmit(event) {
event.preventDefault();
const requiredCourse = courses[0];
const dispatchItem = {
name: requiredCourse.name,
id: requiredCourse.id,
quantity: 1,
mode: mode,
bookType: bookType,
};
if (userLoggedIn) {
console.assert("About to dispatch");
dispatch(add(dispatchItem));
setTimeout(() => {
Router.push("/cart");
}, 2000);
} else {
openModal();
setTimeout(() => {
Router.push({
pathname: "/user/signup",
query: {
redirect: requiredCourse.url,
},
});
}, 6000);
}
}
Every help is really appreciated.

Related

Testing Redux reducer fails to pass

New to unit testing in general and especially in Redux, forgive the dumb question.
I'm trying to test a simple reducer but can't get to make it work.
Reducer:
import { ActionTypes } from "../constants/action-types";
const initialState = {
products: [],
};
export const productReducer = (state = initialState, { type, payload }) => {
switch (type) {
case ActionTypes.FETCH_PRODUCTS:
return { ...state, products: payload };
case ActionTypes.RESET_PRODUCTS:
return {};
default:
return state;
}
};
Test:
import { ActionTypes } from "../../redux/constants/action-types";
import { productReducer } from ".././../redux/reducers/productReducer";
describe("Product reducer", () => {
describe("fetching products", () => {
it("adds products", () => {
const action = {
type: ActionTypes.FETCH_PRODUCTS,
product: [{ name: "test" }],
};
const initialState = undefined;
const nextState = productReducer(initialState, action);
expect(nextState).toEqual([{ name: "test" }]);
});
});
});
That's what I get:
expect(received).toEqual(expected) // deep equality
Expected: [{"name": "test"}]
Received: {"products": undefined}
Just don't understand how to test it.
use payload instead of product
const action = {
type: ActionTypes.FETCH_PRODUCTS,
payload: [{ name: "test" }],
};

I need to change the value of a boolean in an array in react redux

Hey everyone I need to change the value of a boolean in an array in react redux. In the default state it is set to false and I need to set it to true. I am trying to change the state with an onclick event but I cant seem to do it any help I would be happy. When I click on the button reserved it still set to false and I need it set to true so I can make changes.
here it is at the start
`
export const getRockets = createAsyncThunk(
'rocketSlice/getRockets',
async () => {
const response = await fetch('https://api.spacexdata.com/v3/rockets').then(
(data) => data.json()
);
const rocketApiInfo = response.map((rocket) => {
const rockets = {
id: rocket.rocket_id,
rocketName: rocket.rocket_name,
description: rocket.description,
flickrImages: rocket.flickr_images,
reserved: false,
};
return rockets;
});
return rocketApiInfo;
}
);
my reducers
const rocketsSlice = createSlice({
name: 'rockets',
initialState: {
rockets: [],
status: '',
loading: true,
},
reducers: {
reserveRocket(state, action) {
const newState = state.rockets.map((rocket) => {
if (rocket.id === action.payload) {
return { ...rocket, reserved: true };
}
return rocket;
});
return { ...state, rockets: newState };
},
cancelReservation(state, action) {
const newState = state.rockets.map((rocket) => {
if (rocket.id === action.payload) {
return { ...rocket, reserved: false };
}
return rocket;
});
return { ...state, rockets: newState };
},
},
`
the function
const reservedhandler = (e) => {
if (reserved === false) {
dispatch(reserveRocket(e.target.id));
} else {
dispatch(cancelReservation(e.target.id));
}
};
reserved is undefined so maybe that is the problem. But i am not sure how to fix it. Thanks guys

Why items appends to the redux rather than replace?

I'm newbie to Reactjs. The problem I'm encountered:
When Article page loads in the first time, all is fine and there are 10 articles shown. When I click on the browser back button, and then I go to the Article page for the second time, the article-list will be duplicated (so, it will be 20 articles). If I do so again, it will be 30 articles and so on ..
I want to know, why the result of API call appends for the Redux and not replace? In other word, how can I clean the Redux on page load every time? The expected result is seeing always 10 item (articles) on the page Article when I open it.
Here is a simplified of the element (for navigating to the list of articles) in the main page:
import Pages from "Constants/Pages";
const Component = () => {
const history = useHistory();
const navigateWithToken = (page) => {
history.push(page);
}
};
return (
<div className="d-flex align-items-center flex-column py-1 ">
<div
className="main-footer-btn-article"
onClick={() => navigateWithToken(Pages.Articles)}
></div>
<span className="main-footer-btn-text">Articles</span>
</div>
)
};
export const ArticlesBtn = memo(Component);
Also, here is the Article page:
import { memo, useEffect } from "react";
import { useHistory } from "react-router-dom";
import { useSelector, useDispatch } from "react-redux";
import PostItems from "SharedComponents/PostItems";
import { getAllPosts } from "Redux/Actions";
import Pages from "Constants/Pages";
const Page = () => {
const posts = useSelector((state) => state?.articles?.posts?.items);
const dispatch = useDispatch();
const { push } = useHistory();
useEffect(() => {
dispatch(getAllPosts());
}, []);
const onClickPost = (item) => {
push({
pathname: Pages.SingleArticle,
state: {
postId: item.id,
title: item.subject,
is_saved: item.is_saved,
},
});
};
return (
<div className="full-height overflow-auto">
{
posts?.map((item, index) => {
return (
<PostItems
{...item}
key={item.id}
index={index}
onClickPost={() => onClickPost(item)}
/>
);
})
}
</div>
);
};
export default memo(Page);
Also here is the API call:
const getAllPosts = (page = 1) => {
return async (dispatch: ReduxDispatch) => {
//"posts?for=for_website"
dispatch(toggleLoading(true));
try {
const { data } = await axios({
method: "GET",
url: "posts?for=for_mobile",
params: { page: page },
});
const items = data?.data?.data;
const pagination = {
current_page: data.data.current_page,
last_page: data.data.last_page,
};
dispatch(
dispatchItemToRedux({
type: ReducerTypes.GET_ALL_POSTS,
payload: {
items,
pagination,
},
})
);
} catch (err) {
return Promise.reject(err);
} finally {
dispatch(toggleLoading(false));
}
};
};
Also, here is the reducer:
import ReducerTypes from "Redux/Types/ReducerTypes";
const INITIAL_STATE = {
posts: {
items: [],
pagination: {}
},
singlePost: {
id: null,
subject: null,
caption: null,
deep_link: null,
short_link: null,
total_comments: null,
total_likes: null,
total_views: null,
created: null,
medias: null,
likes: []
},
postComments: []
};
function articleReducer(state = INITIAL_STATE, action) {
switch (action.type) {
case ReducerTypes.GET_ALL_POSTS:
return {
...state,
posts: {
items: state.posts.items.concat(action.payload.items),
pagination: action.payload.pagination
}
};
case ReducerTypes.GET_SINGLE_POST:
return {
...state,
singlePost: action.payload
};
case ReducerTypes.GET_POST_COMMENTS:
return {
...state,
postComments: action.payload
};
case ReducerTypes.GET_POST_LIKES:
return {
...state,
singlePost: {
...state.singlePost,
likes: action.payload
}
};
default:
return state;
};
};
export default articleReducer;
case ReducerTypes.GET_ALL_POSTS:
return {
...state,
posts: {
items: action.payload.items,
pagination: action.payload.pagination
}
};
Try omitting that .concat()

Redux-toolkit: state is a proxy inside a createSlice reducer

I am trying to append / update some data in a state array inside of my slice reducers, but when I try to console.log the .projects array of the state that I am interested in I just get a javascript Proxy. What is going on here (what am I doing wrong)?
import { createSlice } from '#reduxjs/toolkit';
const initialState = {
projects: [],
};
const projectsSlice = createSlice({
name: 'projectsSlice',
initialState: { ...initialState },
reducers: {
addProject(state, action) {
const { project } = action.payload;
const newProjects = [project, ...state.projects];
console.group('add project');
console.log('project: ', project);
console.log('state projects: ', state.projects);
console.log('newProjects: ', newProjects);
console.groupEnd();
state.projects = newProjects;
},
setProjects(state, action) {
const { projects } = action.payload;
state.projects = projects;
},
removeProject(state, action) {
const { projectId } = action.payload;
const newProjects = [...state.projects].filter((project) => project.id !== projectId);
state.projects = newProjects;
},
updateProject(state, action) {
const { project } = action.payload;
const projectIndex = state.projects.findIndex((stateProject) => stateProject.id === project.id);
const newProjects = [...state.projects].splice(projectIndex, 1, project);
console.group('updateProject');
console.log('project: ', project);
console.log('projectIndex: ', projectIndex);
console.log('state projects: ', state.projects);
console.log('newProjects: ', newProjects);
console.groupEnd();
state.projects = newProjects;
},
},
});
export const { addProject, removeProject, updateProject, setProjects } = projectsSlice.actions;
export default projectsSlice.reducer;
The Proxy there is the reason you can just mutate state in that reducer and just get an immutable copy in your state - but browsers are really bad at logging proxies.
per the docs, you can use the current export of RTK&immer:
const slice = createSlice({
name: 'todos',
initialState: [{ id: 1, title: 'Example todo' }],
reducers: {
addTodo: (state, action) => {
console.log('before', current(state))
state.push(action.payload)
console.log('after', current(state))
},
},
})
You need to use current
import { current } from '#reduxjs/toolkit'
and with that, you can reach the current state and work with them and after that, you can send a return in reducer to return new data.
Will looks like this:
const referralSlicer = createSlice({
name: 'referral',
initialState: {
referrals: [],
currentCard: 0,
} as IReferralSlicer,
reducers: {
addReferrals(state, { payload }) {
return {
referrals: [...state.referrals, payload],
}
},
deleteReferral(state, { payload }) {
const currentState = current(state)
return {
...currentState,
referrals: currentState.referrals.splice(payload, 1),
}
},
setCurrentCard(state, { payload }) {
return {
referrals: state.referrals,
currentCard: payload,
}
},
},
})

Cannot read property 'type' of undefined (action)

I am using React+Redux. I am getting an error:
While debugging, data from the server came to the state, and the action was empty:
This is part of my code:
class ProfileContainer extends React.Component {
componentDidMount() {
debugger
axios.get('https://social-network.samuraijs.com/api/1.0/profile/3')
.then(response => {
this.props.setUserProfile(response.data)
})
}
}
export default connect(mapStateToProps, { setUserProfile })(ProfileContainer);
My reducer:
const initialState = {
posts: [
{ id: 1, message: "Hey, how are you?", likesCount: 3 },
{ id: 2, message: "This is my first post", likesCount: 15 },
{ id: 3, message: "This is my first post", likesCount: 17 },
],
newPostText: "",
profile: null
};
const profileReducer = (state = initialState, action) => {
debugger
switch (action.type) {
case SET_USER_PROFILE:
return { ...state, profile: action.profile };
default:
return state;
}
};
export const setUserProfile = (profile) => ({ type: SET_USER_PROFILE, profile })
Initiate a dispatch, pass the result to the dispatch() function:
export const setUserProfile = (profile) => (dispatch({ type: SET_USER_PROFILE, profile }))
Your dispatch flow is wrong you have to do like this:
const mapDispatchToProps = dispatch => {
return {
// dispatching plain actions
setUserProfile: (profile) => dispatch({ type: SET_USER_PROFILE, profile }),
}
}
export default connect(mapStateToProps, mapDispatchToProps)(ProfileContainer);

Categories

Resources