I'm using React and Redux for creating shop. I need to add existing item to cart with increasing quantity.
I know, that Redux is based on immutability, but I can't find how to get rid of mutation. Please, give me a hint.
So, there is my code
Actions:
export const UPDATE_QTY = 'UPDATE_QTY';
export const UPDATE_CART = 'UPDATE_CART';
Reducer and initialState:
const initialState = {
cart: [],
qty: 0,
total: 0,
delivery: 5,
};
export const cartReducer = (state = initialState, action) => {
switch (action.type) {
case actions.UPDATE_QTY:
let existedItem = state.cart.filter((cartItem) => cartItem.id === action.payload.id);
existedItem[0].qty = action.payload.qty;
return {
...state,
qty // how to get rid of mutation here?
};
case actions.UPDATE_CART:
return { ...state, cart:[...state.cart, action.payload] };
default:
return state;
}
};
And my Component with dispatch:
export default function AddBtn({ id }) {
const itemData = useSelector((state) => state.main.items);
const cartData = useSelector((state) => state.app.cart);
const dispatch = useDispatch();
const handleAddToCart = () => {
const addedItem = itemData.find((item) => item.id === id);
const existedItem = cartData.find((item) => id === item.id);
if (existedItem) {
dispatch({
type: UPDATE_QTY,
payload: { id, qty: existedItem.qty + 1 },
});
} else {
dispatch({
type: UPDATE_CART,
payload: addedItem,
});
}
return (
// JSX code
)
You can use map function instead, which are immutable:
const initialState = {
cart: [],
qty: 0,
total: 0,
delivery: 5,
};
export const cartReducer = (state = initialState, action) => {
switch (action.type) {
case actions.UPDATE_QTY:
return {
...state,
cart: state.cart.map(el => {
if (el.id === action.payload.id) {
return {
...el,
qty: action.payload.qty
}
}
return el;
})
};
case actions.UPDATE_CART:
return { ...state,
cart: [...state.cart, action.payload]
};
default:
return state;
}
};
You can try this:
const initialState = {
cart: [],
qty: 0,
total: 0,
delivery: 5,
};
export const cartReducer = (state = initialState, action) => {
switch (action.type) {
case actions.UPDATE_QTY:
let existedItem = state.cart.find((cartItem) => cartItem.id === action.payload.id);
if(existedItem){
existedItem.qty = action.payload.qty;
}
return {
...state,
cart:[...state.cart] // how to get rid of mutation here?
};
case actions.UPDATE_CART:
return { ...state, cart:[...state.cart, action.payload] };
default:
return state;
}
};
Related
I am learning react with redux-toolkit. I am stuck with some actions there.
I want to add quantity in Cart, so if I add same item more than once it should be like X1/x2/x3...etc.
And I want to delete items/item but only with the same ID ( when I click delete only delete that one ex. APPLE)
import { createSlice } from "#reduxjs/toolkit";
const initialState = {
hidden: true,
cartItems: 0,
itemsInCart: [],
quantity: 0,
totalCount: 0,
};
export const cartSlice = createSlice({
name: "cart",
initialState,
reducers: {
removeItem: (state, action) => {},
removeAll: (state) => {
state.cartItems = 0;
state.itemsInCart = [];
state.totalCount = 0;
},
addToCart(state, action) {
state.itemsInCar = state.itemsInCart.push(action.payload);
state.cartItems += 1;
state.totalCount += action.payload.price;
},
showCart: (state) => {
state.hidden = !state.hidden;
},
},
});
export const { showCart, addToCart, removeAll, removeItem } = cartSlice.actions;
export default cartSlice.reducer;
addToCart: (state, action) => {
const itemInCart = state.cart.find((item) => item.id === action.payload.id);
if (itemInCart) {
itemInCart.quantity++;
} else {
state.cart.push({ ...action.payload, quantity: 1 });
}
},
My issue is that I want to fetch all products from the database and set them into the Redux initial state, to do this I did an action SET_PRODUCTS_LIST and in the action. payload I simply passed the products fetched in the component (I am using next js), all works fine but when I try to fire another action like ADD_PRODUCT_TO_CART the products in the initial state are gone which it results impossible to add more than 1 product to the cart.
Inside my component:
function Header({ cartProps, setProducts }) {
useEffect(async () => {
const products = await getProducts();
setProducts(products);
}, []);
}
const mapStateToProps = (state) => {
return {
cartProps: state.cartState,
};
};
export default connect(mapStateToProps, {
setProducts,
})(Header);
the action to set products:
import { SET_PRODUCTS_LIST } from "./types";
export const setProducts = (products) => {
return (dispatch) => {
dispatch({
type: SET_PRODUCTS_LIST,
payload: products,
});
};
};
My cart reducer:
const initialState = {
itemNumbers: 0,
cartCost: 0,
products: [],
};
const reducer = (state = initialState, action) => {
switch (action.type) {
case SET_PRODUCTS_LIST: {
return {
...state,
products: action.payload,
};
}
case ADD_PRODUCT_TO_CART: {
//let addQuantity = {
// ...state.products.filter((p) => p.productName === action.paylaod),
// };
console.log(state.products);
return {
itemNumbers: state.itemNumbers + 1,
};
}
default:
return state;
}
};
export default reducer;
maybe I am completely doing wrong the logic about fetching the products in order to have them in the initial state.
const initialState = {
itemNumbers: 0,
cartCost: 0,
products: [],
};
const reducer = (state = initialState, action) => {
switch (action.type) {
case SET_PRODUCTS_LIST: {
return {
...state,
products: action.payload,
};
}
case ADD_PRODUCT_TO_CART: {
//let addQuantity = {
// ...state.products.filter((p) => p.productName === action.paylaod),
// };
console.log(state.products);
return {
...state
itemNumbers: state.itemNumbers + 1,
};
}
default:
return state;
}
};
export default reducer;
You should always return state but in ADD_PRODUCT_TO_CART case you return only
{
itemNumbers: state.itemNumbers + 1,
}
so you need to add ...state before itemNumbers: state.itemNumbers + 1,
I have a redux store as follows:
const initState = {
data: {},
isFetching: false,
};
I have a async function to fetch the data for the state:
const requestSideBarData = async(state, actions) => {
const request = actions.request
let newState = state;
const promises = [];
Object.keys(newState.data).forEach(element => {
promises.push(axios.post(`${ApiEndPoints.getDropDownHeaders}${element}`,request))
});
const results = await Promise.all(promises);
var index = 0;
Object.keys(newState.data).forEach(element => {
newState.data[element] = results[index++].data;
})
return newState;
};
And here are my begin and end reducers:
const fetchSideBarDataBegin = (state) => {
return {
...state,
isFetching: true,
}
};
const fetchSideBarDataEnd = (state) => {
return {
...state,
isFetching: false,
}
};
here's my actions function:
export const fetchSidebBatData = (dateData,selections) => {
return {
type: actions.FETCH_SIDEBAR_DATA_BEGIN, //Here there should be some combine action
dateData: dateData,
selections: selections,
requests: {...dateData,...selections}
}
};
And finally here's my reducer:
const reducer = ( state = initState, action ) => {
switch ( action.type ) {
case actionTypes.FETCH_SIDEBAR_DATA_REQUEST: return fetchSideBarData(state, action);
case actionTypes.FETCH_SIDEBAR_DATA_BEGIN: return fetchSideBarDataBegin(state);
case actionTypes.FETCH_SIDEBAR_DATA_END: return fetchSideBarDataEnd(state);
default: return state;
}
};
What I intend to do is to combine these three reducers into 1: so basically:
FETCH_SIDEBAR_DATA_BEGIN
FETCH_SIDEBAR_DATA_REQUEST
and finally
FETCH_SIDEBAR_DATA_END
what is the right way to perform this operation?
making an eCommerce like platform and here I want to add_to_cart and show the count on the Icon. and and with count show the product as per the id which stored in array
this is my reducers :-
const cartItems = (state = [], action) => {
switch (action.type) {
case 'ADD_TO_CART':
return [...state, action.payload]
case 'REMOVE_FROM_CART':
return state.filter(cartItem => cartItem.id !== action.payload.id)
}
return state
}
export default cartItems
in which i am increasing the count as per the item seleted
const mapStateToProps = reduxStore => {
return {
cartItems: reduxStore
}
}
const mapDispatchToProps = (dispatch) => {
return {
addItemToCart: (product) => dispatch({ type: 'ADD_TO_CART', payload: product })
}
}
export default connect(mapStateToProps , mapDispatchToProps)(ContentPage)
only I am getting the count not the cart items and I want to pass this.state.data.name && this.state.data.img which getting from the URL!
For passing in additional props if that is what you are asking, mapStateToProps also takes an optional props
const mapStateToProps = (reduxStore, ...otherProps) => {
return {
cartItems: reduxStore
}
}
you can reference the data through this.props.cartItems
(Edited) Try updating the state like this:
const initialState = {
cartItems: [],
};
const cartItems = (state = initialState, action) => {
switch (action.type) {
case 'ADD_TO_CART':
return { ...state, cartItems: state.cartItems.push(action.payload) };
case 'REMOVE_FROM_CART':
return {
...state,
cartItems: state.filter(cartItem => cartItem.id !== action.payload.id),
};
return state;
}
};
And to have access to cartItems in the props:
const mapStateToProps = reduxStore => {
return {
cartItems: reduxStore.cartItems
}
}
I just starting on React, and starting to do a todo list. It has functionalities like add, modify(done/pending) and remove task.
Below is my action
export const ADD_TASK = 'ADD_TASK';
export const TOGGLE_TASK = 'TOGGLE_TASK';
export const REMOVE_TASK = 'REMOVE_TASK';
export const FILTER_TASK = 'FILTER_TASK';
let todoId = 1;
export function addTask(task) {
let todo = {
id: todoId++,
name: task,
status: 0,
visible: true
};
return {
type: ADD_TASK,
payload: todo
};
}
export function toggleTask(id) {
return {
type: TOGGLE_TASK,
payload: id
};
}
export function removeTask(id) {
return {
type: REMOVE_TASK,
payload: id
};
}
export function filterTask(id) {
return {
type: FILTER_TASK,
payload: id
};
}
and my reducer :
import { ADD_TASK, TOGGLE_TASK, REMOVE_TASK, FILTER_TASK } from '../actions/index';
let filterStatus = -1;
//initial state is array because we want list of city weather data
export default function(state = [], action) {
// console.log('Action received', action);
const toggling = function (t, action) {
if(t.id !== action)
return t;
return Object.assign({}, t, {
status: !t.status
})
};
const visibility = function(t, action) {
return Object.assign({}, t, {
visible: action === -1 ? true : t.status == action
})
};
switch(action.type) {
case ADD_TASK :
//return state.concat([ action.payload.data ]); //in redux reducer dont modify the state, instead create a new one baesd on old one. Here concat is create a new of old one and add a new data
return [ action.payload, ...state];
case TOGGLE_TASK :
return state.map(s => toggling(s, action.payload)).map(t => visibility(t, filterStatus));
case REMOVE_TASK :
return state.filter(s => { return (s.id != action.payload) } );
case FILTER_TASK :
filterStatus = action.payload;
return state.map(t => visibility(t, action.payload));
}
return state;
}
I read somewhere that modifying state is reducer is a bad practice, yet I feel that I'm doing it in my reducer.
Could anyone suggest the correct way of handling add,remove, update value state in the reducer ?
Thank you
i think you need two reducers: one for managing visibility stuff, one for adding, toggling and removing tasks.
so for the second part i would like do this.
export const ADD_TASK = 'ADD_TASK';
export const TOGGLE_TASK = 'TOGGLE_TASK';
export const REMOVE_TASK = 'REMOVE_TASK';
let todoId = 1;
export addTask = (text) => ({
type: ADD_TASK,
id: todoId++,
text
});
export toggleTask = (id) => ({
type: TOGGLE_TASK,
id
});
export removeTask = (id) => ({
type: REMOVE_TASK,
id
});
export function todosReducer(state = [], action) {
switch(action.type) {
case ADD_TASK :
return [...state, {id: action.id, text: action.text, completed: false}];
case TOGGLE_TASK :
return state.map(task => task.id !== action.id ? task : {...task, completed: !task.completed});
case REMOVE_TASK :
return state.filter(task => task.id !== action.id);
}
return state;
}