react shoping cart mutation issue - javascript

I'm practicing React, developing a Store, for adding the feature of the Shopping Cart I use this example.
But in my implementation, even though my version is almost the same, the "add to cart" button doesn't differentiate between each product, meaning:
1st click affect all the buttons not only the clicked one and the other buttons change to "add more" legend
each posterior click only adds more products of the same kind the user 1st clicked, ignoring if clicked another one.
Seems the error its caused by a mutation in a Reducer Function:
export const CartReducer = (state, action) => {
switch (action.type) {
case 'ADD_ITEM':
if (!state.cartItems.find((item) => item.id === action.payload.id)) {
//-------HERE
state.cartItems.push({
//mutation here?
...action.payload,
quantity: 1,
});
//-------
}
return {
...state,
...sumItems(state.cartItems),
cartItems: [...state.cartItems],
};
What would be an alternative to this?
How do I push items in the state without a mutation?
The complete te file its here
Here you can check the deploy and replicate the error, and here its the correct functionality demo

state.cartItems.push is wrong here, you are mutating the state (antipattern).
first you check whether your item exists as you did at the begging. If the item exists simply return the state. If not you can return
you may return your state as
return {
...state,
...sumItems(state.cartItems + 1),
cartItems: [...state.cartItems, action.payload],
};
Before going into details and complex update operations, you should be familiar with the topics array destruct, object destruct, shallow copy, spread operations, copying arrays, slice, splice, filtering operations. Otherwise you may perform some direct mutation as above.If they are too complex, you can use other libraries which make this operations easier, for example immutable.js
But remember, using extra library will cause a small performance loss.
https://redux.js.org/recipes/using-immutablejs-with-redux

You are mutating the original state, which is bad practice in redux. What you can do is something like this to prevent it:
export const CartReducer = (state, action) => {
switch (action.type) {
case 'ADD_ITEM':
if (!state.cartItems.find((item) => item.id === action.payload.id)) {
// If you have Lint problems with this declaration, you can set a variable before the switch
const cartItems = [...state.cartItems, action.payload];
return {
...state,
...sumItems(cartItems),
cartItems,
};
}
return state;
}
}

Related

How to prevent useSelector from causing unnecessary renders?

I'm using useSelector hook to retrieve the values of my reducer, but it is causing a lot of unnecessary renders on my application.
It doesn't matter which property I'm using on a component, since they are all getting the same state object from the reducer, every time one property changes, all components that use useSelector are rendered.
This is the reducer:
const initialState = {
productsDataState: [], // => this is populated by multiple objects
searchProducts: [],
isSearchOn: false,
inputValue: '',
listOrder: [],
toastify: ['green', ''],
toastifyOpen: false
}
const reducer = ((state = initialState, action) => {
switch (action.type) {
case actionTypes.UPDATE_PRODUCT:
return {
...state,
productsDataState: action.products,
listOrder: action.listOrder
}
case actionTypes.SET_TOASTIFY:
return {
...state,
toastify: action.toastify,
toastifyOpen: action.open
}
case actionTypes.SET_SEARCH:
return {
...state,
searchProducts: action.searchProducts,
isSearchOn: action.isSearchOn,
inputValue: action.inputValue
}
default:
return state
}
})
One of the components is using isSearchOn, which is a boolean, so I solved the problem checking if the value is true before rendering it:
const { isSearchOn } = useSelector(state => state.isSearchOn && state)
But that's not the case for all components. The one I'm stuck right now uses the productsDataState property, which is an array of objects. I can't just make a simple validation before returning state. I thought about storing the initial value in a useState, make a deep comparison between the current value and the past one before returning the state, which would work similarly to what I did in the other component, but I can't see how this would be a good approach.
const { productsDataState } = useSelector(state => state)
Is there a way where I could take advantage of useSelector without comprimising the performance of the application?
I've being reading a lot and making a lot of tests, but I couldn't find a good way to do that so far.
I'd like to keep useSelector, but I'm open to suggestions, even if it involves other libraries.
What you should be doing is not selecting whole state, just the part you need :)
const productsDataState = useSelector(state => state.productsDataState)
#edit
If you want to select multiple data with one selector you will cause it to change reference if you would try to use an object for example.
const { productsDataState, other } = useSelector(state => ({ productsDataState: state.productsDataState, other: state.other }))
this will cause rerender on any state change as redux use strict equality check by default.
You should listen to official documentation and select each state separately
Call useSelector() multiple times, with each call returning a single field value

How can I simplify my reducer function or how long should a simple reducer function be?

I have a reducer function whose sole purpose it to toggle a style on and off. This is a global style and this is why it is in my Redux store.
However, the code looks overly obtuse for such a simple toggle.
const Style = (state = {current: true}, action) => {
if(action.type === "toggleAppStyle"){
const newState = { ...state };
newState.current = !state.current;
return newState;
}
return state;
};
I recently realized that redux runs all reducer functions for each single action, which I find strange, so the returned state must equal the initial state if the action.type is not called for that particular reducer.
Here is the one place I use it:
// ... snip
const mapStateToProps = state => {
return {
Style: state.Style
}
}
// ... snip
render() {
return (
<img className = 'icon_all'
id = {this.props.Style.current === true ? 'icon_10' : 'icon_90'}
onClick = {this.clickHandler} src='/images/favicon-optimized.svg'/>
)
}
the code looks overly obtuse for such a simple toggle.
Overly obtuse is a bit much, this is a pretty standard immutable update pattern, i.e. shallow copy into new object reference and update the necessary properties.
It can't get much simpler than what you've already got, but here is an example returning the new object directly.
const Style = (state = {current: true}, action) => {
if (action.type === "toggleAppStyle") {
return {
...state,
current: !state.current,
};
}
return state;
};
I recently realized that redux runs all reducer functions for each
single action, which I find strange, so the returned state must equal
the initial state if the action.type is not called for that particular
reducer.
Nothing strange here really. The reducer function either acts on the action and operates on the state, thus returning a new state reference and value, otherwise it simply returns the current state value. Other than the action part this is exactly how React reconciliation works. When you update state you are creating new object references or primitive values. This is what triggers rerenders.
Of course, if your objective is to be as short and succinct as possible, you could reduce the state slice to just the boolean value and use a ternary operator to return the toggled state or current state value. Adjust your redux selectors accordingly.
const Style = (state = true, action) => action.type === "toggleAppStyle"
? !state
: state;

delete not working in ternary in setState

So I have been struggling with getting this section of the application working 100% as can be seen with these related questions:
Method renders correctly when triggered one at a time, but not using _.map in React-Redux container
Object passed into Redux store is not reflecting all key/values after mapStateToProps
So the setup is this... a bunch of buttons are dynamically generated based on the number of data "cuts" for a specific item (basically different ways of looking at the data like geography, business segment, etc.). The user can select one button at a time, click a Select All. This will retrieve the data related to the cut from the server and generate a table below the buttons.
One-at-a-time selections is working, select all is working, clear all is working. However, what I am trying to setup now is if the person clicks the same button again, it toggles that data point off.
This is where I was left after one of my previous questions:
onCutSelect(cut) {
this.setState(
({cuts: prevCuts}) => ({cuts: {...prevCuts, [cut]: cut}}),
() => this.props.bqResults(this.state.cuts)
);
}
Works fine for one at a time selections and the Select All (this function is called via a map from a different function).
Modified to this, which I was hoping would toggle the data point off:
onCutSelect(cut) {
this.setState(
({cuts: prevCuts}) => (
this.state.cuts.hasOwnProperty(cut)
?
delete this.state.cuts[cut]
:
{cuts: {...prevCuts, [cut]: cut}}),
() => this.props.bqResults(this.state.cuts)
);
What I would think should be happening, is checks if the key is there and if it is to delete it which will toggle the button off. And it does, it changes the button status to unselected.
However, what I would think should also happen, is since this.state.cuts is being modified, it will send the new this.state to the this.props.bqResults action. While the button is toggling off, it still shows the data related to the cut, so that store is not being updated for whatever reason. How should I be handling this?
Here is the remainder of the related code:
// results.js actions
export function bqResults(results) {
console.log(results); // shows all of the selected cuts here
return function(dispatch) {
dispatch({
type: FILTER_RESULTS,
payload: results
})
}
}
// results.js reducer
import {
FILTER_RESULTS
} from '../actions/results';
export default function(state = {}, action) {
switch(action.type) {
case FILTER_RESULTS:
console.log(action.payload); //prints out all the cuts
return {
...state,
filter_results: action.payload
}
default:
return state;
}
return state;
}
const rootReducer = combineReducers({
results: resultsReducer,
});
export default rootReducer;
onCutSelect(cut) {
this.setState(
({cuts: prevCuts}) => {
if (cuts.hasOwnProperty(cut)) {
const newCut = {...this.state.cuts}
delete newCut[cut]
return newCut
} else {
return {cuts: {...prevCuts, [cut]: cut}}
}
},
() => this.props.bqResults(this.state.cuts)
);
}
A few things. First, don't mutate state directly like that. Using delete removes the index number of the existing this.state.cuts. Perform this operation in a way that creates a completely new array when assigning your new value. I use the spread operator for this.
Also, when you return a delete operation, it's not returning what the array is after using delete. It returns delete's return value, which in this case is a boolean.
function delIdx(arr, idx) {
return delete arr[idx]
}
console.log(delIdx([1,3,4], 3))

Pass by reference assignment mutates redux state

I have an app with a menu of items, and at some point a user may edit the values of the items. When the user does so, I create a copy of the item in a seperate state branch instead of changing the original menu items. So my reducer looks like this:
const menuReducer = (state = [], action) => {
switch (action.type) {
case ADD_ITEM:
return [...state, {id: action.itemId, propA: action.itemPropA, propB: action.itemPropB}]
}
}
const editingMenuItem = (state = {}, action) => {
switch (action.type) {
case SET_EDIT_ITEM:
return {id: action.id, propA: action.itemPropA, propB: action.itemPropB}
case EDIT_ITEM:
return {id: state.id, propA: action.itemPropA, propB: action.itemPropB}
}
}
Someone selects that they want to edit an item, and this causes the dispatchEditItem thunk to trigger and create a copy in the state tree:
const dispatchEditItemThunk = itemId => (dispatch, getState) => {
const item = _.find(getState().menu, ['id', itemId]);
dispatch(setEditItem(item.id, item.propA, item.propB))
}
Then when someone wants to edit a prop, the editingThunk is dispatched:
const editingThunk = (itemId, propName) => (dispatch, getState) => {
let activeItem = getState().editingMenuItem;
// someValue is generated here
activeItem[propName] = someValue
dispatch(editItem(activeItem.propA, activeItem.propB))
}
The problem with this is that when activeItem[propName] = someValue happens, this changes the value of the item contained in the menuReducer array. I'm assuming because everything is pass by reference, and all the references lead back to the original value in the menuReducer. However, this isn't the way I would expect this to work. My assumption would be that calling getState would return a deep copy of the state, and not allow for these kinds of accidental mutations.
Is this a bug? If it isn't, is there a preferred way of writing thunks that avoids this kind of situation? In my real use case, the structure of the props in the menuItem is very complex, and it is handy to create an activeItem in the thunk and mutate it's values before dispatching to the state tree. Is doing this bad?
That's not a bug and mutating state object is highly discouraged. You can create a deep copy of an object using Object.assign and JSON.stringify methods as described here https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign (Examples section).
If Redux was to create a deep copy of state on each dispatch call it could be more secure but also much slower.

redux: Performance modifying property of an object in array using immutable approach. Pros and cons

In my basic understanding about the functional programming way and the way that I need generate the next state in redux when a specific array's change, i've been trying this approach for modify a todo item by index in a todo list:
const toggleSelectedTodo = (todos, selectedIndex) => {
return todos.map((todo, index) => (
{ ...todo, completed: index === selectedIndex ? !todo.completed : todo.completed }
))
}
export default function reducer(state = INITIAL_STATE, action) {
switch (action.type) {
...
case "TOGGLE_SELECTED":
return {
...state,
todos: toggleSelectedTodo(state.todos, action.index)
}
...
default:
return state
}
}
So, my idea it's that I need re-map the all the todo item objects maintaining their values except for the target item. But, I think that isn`t performant and I'm worry about the performance for large arrays and the worst case.
Do you know a better strategies for modify a object property inside an array or this it's the right way for do this?
You should only modify the object with selected index. There's no need to use the spread operator for EVERY object in the array. That can be the potential performance bottleneck. To avoid that your toggleSelectedTodo function should look like this:
const toggleSelectedTodo = (todos, selectedIndex) => {
return todos.map((todo, index) => {
if(index === selectedIndex) {
return { ...todo, completed: !todo.completed }
}
return todo
})
}
Besides that you shouldn't worry about the performance unless you're processing thousands of items.

Categories

Resources