react native shopping cart with redux - javascript

Hello i just started redux with react native i am trying to make a food delivery app. each food has options like drinks or dessert for example. I would like that if the user adds a basket an items we check if the options chosen are the same to increase the quantity if they are not the same I would like to add another item with the new option to the global state. only I tried a lot of things and none seemed to meet my expectations. the structure of the data that I pass to the global state is in this form.
cartProduct = [
{
id: itemId,
restaurantName: restaurantName,
name: itemName,
quantity: quantity,
price: price
selectedOption: [
optionId: optionId,
itemId: itemId,
name: itemName,
]
}
]
Here is the code I wrote in the reducers with the add to cart action
switch (action.type) {
case 'ADD_TO_CART':
const cartProductIndex = state.cartProduct.findIndex(item => item.id === action.value.id)
if (cartProductIndex == -1) {
nextState = {
...state,
cartProduct: [...state.cartProduct, action.value]
}
} else {
state.cartProduct[cartProductIndex].selectedOption.map(item => {
if (item.item === action.value.selectedOption.item) {
let newArray = [...state.cartProduct]
newArray[cartProductIndex] = {
...newArray[cartProductIndex],
quantity: state.cartProduct[cartProductIndex].quantity + 1,
totalPrice: state.cartProduct[cartProductIndex].totalPrice + action.value.totalPrice
}
nextState = {
...state,
cartProduct: newArray
}
} else {
nextState = {
...state,
cartProduct: [...state.cartProduct, action.value]
}
}
})
}
return nextState || state

I would do something like this.
switch (action.type) {
case 'ADD_TO_CART':
const cartProductIndex = state.cartProduct.findIndex(item => item.id === action.value.id)
if (cartProductIndex == -1) {
return {
...state,
cartProduct: [...state.cartProduct, action.value]
}
}
const newCartProduct = state.cartProduct[cartProductIndex].selectedOption.map(item => {
if (item.item === action.value.selectedOption.item) {
item.quantity++
}
return item
})
return {
...state,
cartProduct: newCartProduct
}
}
You cannot update the state in a map loop, a map returns a new array. You want to use map to create a new array, and then use that value. Once a function is complete you should return, you don't need that else.

Related

How to add a new item into array in an object that's is in the array?

Here is my state
const initState = [
{
id: 1,
criteria: [],
isInclusion: true,
},
{
id: 2,
criteria: [],
isInclusion: true,
},
];
I am trying to add a new object into criteria array with my dispatch
dispatch(
addCriteria({
id: item.id,
type: item.type,
groupId: 1,
})
);
in the reducer I do the following but it doesnt add to an array of an existing group but instead, it adds as a new object to the array with that criteria added in it.
const addReducer = (state = initState, action) => {
switch (action.type) {
case QUERYGROUPS.ADD_CRITERIA: {
const newCriteria = {
id: action.payload.id,
type: action.payload.type,
};
const newState = [
...state,
state.map(group =>
group.id === action.payload.groupId
? {
...group,
criteria: newCriteria,
}
: group
),
];
return newState;
}
}
};
You are adding all items to the state object (also the one you want to modify) with ...state and than you map over it again. This will not work.
You should change your state object to be an object to access the items by reference and not index and use the id as access. This will also be faster(thanks #Yash Joshi ):
const initState = {
1: {
criteria: [],
isInclusion: true,
},
2: {
criteria: [],
isInclusion: true,
},
};
This will let you access and update the state more easily and easier to stay immutable.
This will let you update it like this:
case QUERYGROUPS.ADD_CRITERIA: {
const newCriteria = {
id: action.payload.id,
type: action.payload.type,
};
const newState = {
...state,
[action.payload.groupId]: {
...state[action.payload.groupId],
criteria: [
...state[action.payload.groupId].criteria,
newCriteria
],
}
};
return newState;
To add a new item to it:
const newState = {
...state,
[action.payload.groupId]: {
isInclusion: false,
criteria: [ ],
}
};
Hope this helps. Happy coding.
Try spread operator (es6):
return [
...state,
newCriteria,
]
This will return a new array with the newCriteria object on it.
Here is how I'd do it:
const newCriteria = {
id: action.payload.id,
type: action.payload.type,
};
const newState = state.map(gr => {
if (gr.id !== action.payload.groupId) {
return gr;
}
return {
...gr,
criteria: [...gr.criteria, newCriteria];
}
});
I think you should follow Domino987 approach it will be faster. But if you still wish to continue with your approach. You can try this:
const newState = state.map(item => item.id === newItem.id ? ({ ...item, ...newItem }) : item );
return newState;
Hope this Helps!

React component does not update with the change in redux state

I have a cart data in this form
const cart = {
'1': {
id: '1',
image: '/rice.jpg',
price: 32,
product: 'Yellow Corn',
quantity: 2,
},
'2': {
id: '2',
image: '/rice.jpg',
price: 400,
product: 'Beans',
quantity: 5,
},
'3': {
id: '3',
image: '/rice.jpg',
price: 32,
product: 'Banana',
quantity: 1,
},
};
In the reducer file I have a function removeItem that is being consumed by the reducer
const removeItem = (items, id) => {
items[id] && delete items[id];
return items;
};
case REMOVE_ITEM: {
const { cart } = state;
const {
payload: { id },
} = action;
return {
...state,
cart: removeItem(cart, id),
};
}
In the component I am using this handleRemove() to handle the deletion
handleRemove = id => {
const {
actions: { removeItem },
} = this.props;
const payload = { id };
removeItem(payload);
};
Now in the redux developer tool, the change is working effectively but the component view is not updating.
Change removeItem function to below code
const removeItem = (items, id) => {
items[id] && delete items[id];
return {...items};
};
This is because component gets change only if reference changes. You can refer this link for more explanation
You need to create a copy of the cart, as otherwise React won't detect the change, because it does reference comparison and you return the same object.
Try to do the removeItem() in this way.
const removeItem = (items, id) => {
let itemsClone = [...items]; // Copies all items into a brand new array
itemsClone [id] && delete itemsClone [id]; // You perform the delete on the clone
return itemsClone ; // you return the clone
};
Do not mutate redux state, redux does not perform a deep diff check in your objects, when you do not mutate and create new objects, it is automatically detected as a different object, because its plain old js objects.
this would be good for further reading : immutable-update-patterns
so your removeItem method should be,
const removeItem = (items, id) => {
let {[id]: remove, ...rest} = items
return rest;
}
You can also use a library to do this, such as dot-prop-immutable , which has set, remove, merge methods to do relevant operations without mutating the object.

How do I properly update an item in my array in redux without mutating?

const state = [
{
list: []
}
];
The list is a list of student objects, for example:
list: [
{ id: 1, name: "Mark", attendance: true },
{ id: 2, name: "John", attendance: false }
]
I have a button that triggers a post request to an API to change attendance to true. Post request returns the student object that was changed as in e.g.:
{ id: 2, name: "John", attendance: true }
This works fine and if no errors, will dispatch ATTENDANCE_SUCCESS.
Now, with this kind of set-up:
export function students(state, action) {
let latestState = state[state.length - 1],
newState = Object.assign({}, latestState);
switch (action.type) {
case "ATTENDANCE_SUCCESS":
if (action.res.errorCode == 0) {
// Need to change redux state 'attendance' value to true for a student with ID returned from the POST request
}
}
Initially, I did:
const studentChanged = newState.list.find(function(student) {
return (
student.id ===
action.res.data.id
);
});
studentChanged.attendance = true;
But it mutates the state in the redux store (although I am not sure how it's exactly happening since I assumed newState is already a copy).
What's the proper way?
The following would update a single item in the array. The critical aspect here is that if the id of the item does not match the id from the action payload, it returns the item unaltered, otherwise it updates the attendance property. Array.prototype.map returns a new array so it would be immutable.
export function students(state, action) {
switch (action.type) {
case "ATTENDANCE_SUCCESS":
if (action.res.errorCode == 0) {
return state.map(student => {
// we want to leave non matching items unaltered
if (student.id !== action.res.data.id) {
return student;
}
return { ...student, attendance: true };
});
}
return state;
default:
return state;
}
}
Here is a StackBlitz to demonstrate the functionality.
Hopefully that helps!

Query inside reducer ? redux

How do i write this inside of an reducer to change the state?
doc = {
id:"zf123ada123ad",
name:"examp",
subdoc:{
name:"subdoc examp",
subsubdoc:[{
id:"zcgsdf123zaar21",
subsubsubdoc:[{
id:"af2317bh123",
value: "heyhey" //this value I want to update
}]
}]
}
}
let's say i have an reducer that looks like this
The action.payload look something like this
{
theInputId1: "someId",
theInputId2: "anotherId",
theInputValue: "someValue"
}
export function updateSubSubSubDoc(state = {}, action){
switch(action.type){
case 'UPDATE_THE_SUBSUBSUB':
return {
state.doc.subdoc.subsubdoc.find(x => x.id ==
theInputId1).subsubsubdoc.find(x => x.id == theInputId2).value = theInputValue // just example code for you to understand where i'm going.
}
default:
return state
}
}
What I want to do it update one subsubsub doc in a state that is current
With ES6, this is one way that you could do that:
const initialState = { doc: { subdoc: { subsubdoc: {} } } };
export function doc(state = initialState, action) {
switch (action.type) {
case 'UPDATE_THE_SUBSUBSUB':
const subsubdocIdx = state.doc.subdoc.
subsubdoc.find(s => s.id == action.theInputId1);
const subsubdoc = state.doc.subdoc.subsubdoc[subsubdocIdx];
const subsubsubdocIdx = state.doc.subdoc.
subsubdoc[subsubdocIdx].
subsubsubdoc.find(s => s.id == action.theInputId2);
const subsubsubdoc = state.doc.subdoc.
subsubdoc[subsubdocIdx].
subsubsubdoc[subsubsubdocIdx];
return {
...state,
doc: {
...state.doc,
subdoc: {
...state.doc.subdoc,
subsubdoc: [
...state.doc.subdoc.subsubdoc.slice(0, subsubdocIdx),
{
...subsubdoc,
subsubsubdoc: [
...subsubdoc.slice(0, subsubsubdocIdx),
{
...subsubsubdoc,
value: action.theInputValue,
},
...subsubdoc.subsubsubdoc.slice(subsubsubdocIdx + 1, subsubdoc.subsubsubdoc.length - 1),
],
},
...state.doc.subdoc.subsubdoc.slice(subsubdocIdx + 1, state.doc.subdoc.subsubdoc.length - 1),
]
}
}
}
default:
return state;
}
}
(I haven’t tested this code.)
This is nested the same level as in your example, but you might consider using something like combineReducers to make this a little easier to manage. This is also presupposing you have other actions that create the document chain along the way, and you know these documents exist.
Here's an example how you might be able to do it with combineReducers:
function doc(state = {}, action) {
}
function subdoc(state = {}, action) {
}
function subsubdoc(state = [], action) {
}
function subsubsubdoc(state = [], action) {
switch (action.type) {
case 'UPDATE_THE_SUBSUBSUB':
const idx = state.find(s => s.id == action.theInputId2);
return [
...state.slice(0, idx),
{
...state[idx],
value: action.theInputValue,
},
...state.slice(idx + 1),
];
default:
return state;
}
}
export default combineReducers({
doc,
subdoc,
subsubdoc,
subsubsubdoc,
});
In this example, you don't need action.theInputId1, but you would need to store some reference in the data from the subsubdoc to the subsubsubdoc so that when you're rendering, you can piece it back together. Same with all of the other layers.

How to make this piece of code look better

This is one of my redux reducers and I feel it looks very ugly. Is it possible to improve it?
The goal that I want to achieve is very simple:
If I already have this item in my current state, increase the quantity by 1,
otherwise add this item to state.
function globalReducer(state = initialState, action) {
switch (action.type) {
case ADD_TO_CART: {
let { item } = action;
if (state.getIn(['sideCart', 'orderItems', item.id])) {
item.quantity = state.getIn(['sideCart', 'orderItems', item.id]).get('quantity') + 1;
} else {
item.quantity = 1;
}
item = fromJS(item);
const newState = state.setIn(['sideCart', 'orderItems', item.get('id')], item);
return newState;
}
default:
return state;
}
}
The state should look like this:
sideCart: {
orderItems: {
1: {
id: 'orderItems-1',
name: 'AI Brown\'s BBQ Fillet of Beef with Brown Mushroom Brandy Sauce',
quantity: 10,
price: 12,
subitems: ['0', '1', '2'],
instruction: 'No rosemary for beef',
},
2: {
id: 'orderItems-2',
name: 'AI Brown\'s BBQ Fillet',
quantity: 10,
price: 14,
subitems: ['0', '1', '2'],
instruction: 'No rosemary for beef',
},
},
}
This is how I would enhance it syntactically:
const reduceCart = (state, action) => {
let { item } = action;
const stateIn = state.getIn(['sideCart', 'orderItems', item.id]);
item.quantity = stateIn
? stateIn + 1
: 1;
item = fromJS(item);
return state.setIn(['sideCart', 'orderItems', item.get('id')], item);
};
const globalReducer = (state = initialState, action) => {
switch (action.type) {
case ADD_TO_CART: return reduceCart(state, action);
default: return state;
}
};
I found the same complexity when using immutable.js to handle deeply nested objects. I have made a lightweight immutable helper: ImmutableAssign that allows you to continue working with plain JS objects, which will simplify your operations.
In the following example, it expects state and action to be plain JS objects, and it will return you a new state as plain JS object:
function globalReducer(state = initialState, action) {
switch (action.type) {
case ADD_TO_CART: return addCart(state, action);
default: return state;
}
}
function addCart(state, action) {
let { item } = action;
return iassign(state,
function(state, context) {
return state.sideCart.orderItems[context.item.id]
},
function(selectedItem) {
item.quantity = selectedItem.quantity ? selectedItem.quantity + 1 : 1;
return item;
},
{ item: item });
}
// The first parameter is a function that return the
// property you need to modify in your state.
// This function must be **pure function**, therefore "item"
// need to be passed in via the context parameter.
//
// The second parameter is a function that modify selected
// part of your state, it doesn't need to be pure, therefore
// you can access "item" directly
//
// The third parameter is the context used in the first
// function (pure function)

Categories

Resources