React component does not update with the change in redux state - javascript

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.

Related

saveData method saves twice

I am building a React app that includes one separate component for CRUD functionality of Products and another separate component for CRUD functionality of Suppliers.
I am using the same saveData method for both components (the Create functionality of CRUD.. that is triggered when the User presses Save after filling in the input fields of Product or Supplier). The saveData method is located in a central ProductsAndSuppliers.js file that is available to both the Products and Supplier components.
In both of the Product & Supplier components, there is a table showing the Products or Suppliers already present as dummy data.
I made a button at the bottom of each page to add a new Product or Supplier... depending on which tab the user has selected on the left side of the screen (Product or Supplier).
Since I am using the same saveData method in both cases, I have the same problem whenever I try to add a new Product or Supplier to each respective table after filling out the input fields. My new Product or Supplier is added.. but twice and I can't figure out why.
I have tried using a spread operator to add the new item to the collection but am having no success:
saveData = (collection, item) => {
if (item.id === "") {
item.id = this.idCounter++;
this.setState((collection) => {
return { ...collection, item }
})
} else {
this.setState(state => state[collection]
= state[collection].map(stored =>
stored.id === item.id ? item : stored))
}
}
Here is my original saveData method that adds the new Product or Supplier, but twice:
saveData = (collection, item) => {
if (item.id === "") {
item.id = this.idCounter++;
this.setState(state => state[collection]
= state[collection].concat(item));
} else {
this.setState(state => state[collection]
= state[collection].map(stored =>
stored.id === item.id ? item : stored))
}
}
my state looks like this:
this.state = {
products: [
{ id: 1, name: "Kayak", category: "Watersports", price: 275 },
{ id: 2, name: "Lifejacket", category: "Watersports", price: 48.95 },
{ id: 3, name: "Soccer Ball", category: "Soccer", price: 19.50 },
],
suppliers: [
{ id: 1, name: "Surf Dudes", city: "San Jose", products: [1, 2] },
{ id: 2, name: "Field Supplies", city: "New York", products: [3] },
]
}
There are issues with both of your implementations.
Starting with the top one:
// don't do this
this.setState((collection) => {
return { ...collection, item }
})
In this case, collection is your component state and you're adding a property called item to it. You're going to get this as a result:
{
products: [],
suppliers: [],
item: item
}
The correct way to do this with the spread operator is to return an object that represents the state update. You can use a computed property name to target the appropriate collection:
this.setState((state) => ({
[collection]: [...state[collection], item]
})
)
* Note that both this and the example below are using the implicit return feature of arrow functions. Note the parens around the object.
In the second code sample you're
mutating the existing state directly which you should not do.
returning an array instead of a state update object.
// don't do this
this.setState(state =>
state[collection] = state[collection].concat(item)
);
Assignment expressions return the assigned value, so this code returns an array instead of an object and I'd frankly be surprised if this worked at all.
The correct implementation is the same as above except it uses concat instead of spread to create the new array:
this.setState(state => ({
[collection]: state[collection].concat(item)
})
);
needlessly fancy, arguably silly id generators:
const nextId = (function idGen (start = 100) {
let current = start;
return () => current++;
})(100);
console.log(nextId()); // 100
console.log(nextId()); // 101
console.log(nextId()); // 102
// ----------------
// a literal generator, just for fun
const ids = (function* IdGenerator(start = 300) {
let id = start;
while (true) {
yield id++;
}
})();
console.log(ids.next().value); // 300
console.log(ids.next().value); // 301
console.log(ids.next().value); // 302

How do you prevent a javascript object from being converted into a length 1 array of objects?

I'm working on my first solo ReactJS/Redux project and things were going well until I got to a point where I'm using an object in the Redux store that is always supposed to be a single object. When I copy the object from one part of the store (one element of the sources key) to another (the selectedItems key) that object is being stored as an array of length 1, which isn't the data I'm passing in (it's just a single object). I could live with this and just read out of that store variable as an array and just use element 0 except that when I call another method in the reducer to replace that variable in the store, that method stores the new data as a single object! My preference would be to have everything store a single object but I can't figure out how to do that. Anyway, here's some of the reducer code:
const initialState = {
sources: [
{
id: 1,
mfg: 'GE',
system: 'Smart bed',
model: '01',
name: 'GE smart bed'
},
{
id: 2,
mfg: 'IBM',
system: 'Patient monitor',
model: '03',
name: 'IBM patient monitor'
}
],
error: null,
loading: false,
purchased: false,
selectedItem: {}
};
// This is called when a user selects one of sources in the UI
// the Id of the selected sources object is passed in as action.id
// This method creates an array in state.selectedItem
const alertSourceSelect = ( state, action ) => {
let selectedItem = state.sources.filter(function (item) {
return item.id === action.id;
});
if (!selectedItem) selectedItem = {};
return {...state, selectedItem: selectedItem};
};
// When someone edits the selected source, this takes the field name and new value to
// replace in the selected source object and does so. Those values are stored in
// action.field and action.value . However, when the selected source object is updated
// it is updated as a single object and not as an array.
const selectedSourceEdit = ( state, action ) => {
return {
...state,
selectedItem: updateObject(state.selectedItem[0], { [action.field] : action.value })
};
};
const reducer = (state = initialState, action) => {
switch (action.type) {
case actionTypes.ALERT_SOURCE_SELECT: return alertSourceSelect( state, action );
case actionTypes.ALERT_SELECTED_SOURCE_EDIT: return selectedSourceEdit( state, action );
default: return state;
}
Here is the updateObject method (sorry I left it out):
export const updateObject = (oldObject, updatedProperties) => {
return {
...oldObject,
...updatedProperties
}
};
Issue : updateObject is returning object and not array,and you are maintaining selectedItem as an array not object
export const updateObject = (oldObject, updatedProperties) => {
return {
...oldObject,
...updatedProperties
}
};
Solution :
Either return array from updateObject :
export const updateObject = (oldObject, updatedProperties) => {
return [{
...oldObject,
...updatedProperties
}]
};
OR make array of returned object
const selectedSourceEdit = ( state, action ) => {
return {
...state,
selectedItem: [updateObject(state.selectedItem[0], { [action.field] : action.value })]
};
};

How do you move an object from one array to another array by feature?

I am trying to move an object from one array to another. Think of it like adding / moving a friend from non-friend to friend. I have two arrays, which can be seen below, and I am trying to move an object (i.e. a friend) from possible to current via it's 'id'. In the below example, I am trying to move Parker from possible to current with id = 2.
state = {
current: [
{
id: 1,
name: 'peter'
}
],
possible: [
{
id: 2,
name: 'parker'
}
]
}
function addFriend(state, action) {
const { current, possible } = state;
const addedFriend = Object.assign(
{},
state.possible.splice(action.payload.index, 1)
);
current.push(addedFriend);
const newState = { current, possible };
return newState;
}
Since you can remove multiple elements with splice(), it returns an array. Index the result to get the specific object. You don't need to use Object.assign(), that just copies the value (which just converts the array into an object whose properties are the array indexes).
var state = {
current: [
{
id: 1,
name: 'peter'
}
],
possible: [
{
id: 2,
name: 'parker'
}
]
};
function addFriend(state, action) {
const { current, possible } = state;
const addedFriend = state.possible.splice(action.payload.index, 1)[0];
current.push(addedFriend);
const newState = { current, possible };
return newState;
}
state = addFriend(state, {payload: { index: 0 }});
console.log(state);
I'm not sure why you're returning a new state object, since you're modifying the old state in place.
It is not that time-efficient if you want a fast running code. But it follows immutability.
We just ignore the item from possible, and add it to current.
state = {
current: [
{
id: 1,
name: 'peter'
}
],
possible: [
{
id: 2,
name: 'parker'
}
]
}
function addFriend(state, action) {
const { current, possible } = state;
return {
...state,
current: current.concat(possible[action.payload.index]),
possible: possible.filter((_, index) => index !== action.payload.index)
}
}
state = addFriend(state, {payload: {index: 0}})
console.log(state)

How do i setState with hooks of an object which has multiple arrays ? How to setdebtors here?

I am deleting an one id in an array, how do I setState after filtering it here?
https://codesandbox.io/s/react-example-1m2qn
const Debtors = () => {
const debtors = [
{
id: 1,
name: "John",
relation: "friend",
statement: [
{ id: 1, date: 2010, amount: "1000", purpose: "John" },
{ id: 2, date: 2014, amount: "2000", purpose: "john" }
]
},
,
{
id: 2,
name: "Jack",
relation: "Friend",
statement: [
{ id: 1, date: 2010, amount: "1000", purpose: "jack" },
{ id: 2, date: 2014, amount: "2000", purpose: "jack" }
]
}
];
const [newDebtors, setdebtors] = React.useState(debtors);
const handleDelete = (stat, i) => {
const newList = newDebtors[0].statement.filter(x => x.id !== stat.id);
// How to set debtors here ?
// setdebtors({ ...newDebtors, statement[0]: newList });
console.log(newList)
// How to set debtors here ?
There's two problems:
1) You are iterating off the original debtors object in your render, instead of the newDebtors state you created via useState(), which is why there does not appear to be any UI change.
You need: newDebtors[0].statement.map
2) You need to pass in the item index in your handleDelete() so it knows what item in the array to update. You can have the function do something like this:
In the onClick:
<a
href="javascript:;"
onClick={() => handleDelete(stat, i, 0)}
>
In the handleDelete():
const handleDelete = (stat, i, arrayIndex) => {
const updatedDebtors = newDebtors.map((item, index) => {
if (index === arrayIndex) {
return {
...item,
statement: item.statement.filter(
statement => statement.id !== stat.id
)
};
} else {
return item;
}
});
setDebtors(updatedDebtors);
};
See sandbox for full solution: https://codesandbox.io/s/react-example-x7uoh
You should do it like that:
setdebtors((prevState) => {
let newArray = Array.from(prevState); // Copy the array
// Manipulate the array as you wish
return newArray; // return it
});
The problem is you are mutating the array of "debtors" you need to map through the array of debtors and change any properties in the object.
const handleDelete = (stat, i) => {
const newList = newDebtors.map((debtor, i) => {
if (i === 0) {
debtor.statement = debtor.statement.filter(x => x.id !== stat.id);
}
return debtor;
});
setdebtors(newList);};
An even better approach is to use "useReducer" which is used for mutating more complex pieces of state, like you have here. THe docs are very helpful useReducer
Hmm I dont know what exactly you are trying to do,
Is this what you are looking for?
const handleDelete = (stat, i) => {
const newList = newDebtors[0].statement.filter(x => x.id !== stat.id);
const newFirstItem = {...newDebtors[0],statement: newList}
const newDebtorList = newDebtors.filter(x => x.id !== newFirstItem.id);
newDebtorList.unshift(newFirstItem);
setdebtors(newDebtorList);
}
I know this seems complex but you kinda actually need to do this as you cannot mutate an array in the state...
What I did here is I first created a new statement list(newList), then created a newFirstItem to be set as the new newDebtors[0], then created a new array(newDebtorList) of all the elements of newDebtors except the first one, I modified this array by pushing the newFirstItem to the 0th position(using unshift)
Finally updated the state with this new array...
hope it helps :)
Note: this is for changing the 0th element if you have the id please change the code accordingly

How do I update an array of objects in component state?

I am trying to update the property of an object which is stored in an array.
my state looks something like this:
state = {
todos: [
{
id: '1',
title: 'first item,
completed: false
},
{
id: '2',
title: 'second item,
completed: false
}
],
}
What I am trying to do is access the second element in the 'todos' array and update the completed property to either false -> true or true -> false.
I have a button with the handler for update, and my class method for the update looks like this:
onUpdate = (id) => {
const { todos } = this.state;
let i = todos.findIndex(todo => todo.id === id);
let status = todos[i].completed
let updatedTodo = {
...todos[i],
completed: !status
}
this.setState({
todos: [
...todos.slice(0, i),
updatedTodo,
...todos.slice(i + 1)
]
});
}
While this does work, I want to find out if there is a more concise way of achieving the same result; I tried to use Object.assign(), but that didn't work out because my 'todos' is an array, not an object. Please enlighten me with better code!
It would be best to use update function to make sure you don't work on outdated data:
onUpdate = (id) => {
this.setState(prevState => {
const copy = [...prevState.todos];
const index = copy.findIndex(t => t.id === id);
copy[index].completed = !copy[index].completed;
return { todos: copy }
})
}
You can simply copy your todos from state, then make edits, and after that put it back to the state
onUpdate = (id) => {
var todos = [...this.state.todos]
var target = todos.find(todo => todo.id == id)
if (target) {
target.completed = !target.completed
this.setState({ todos })
}
}

Categories

Resources