ReactJS: How to change value of specific field in an object array? - javascript

constructor (props) {
super(props)
this.state = { items: this.props.items }
// items props is: [{'id':'73','foo':'bar'},{'id':'45','foo':'bar'}]
}
onClick () {
const myArray = this.state.items
const ids = ['45', '73']
ids.forEach((id, index) => {
myArray.find(x => x.id === id).foo = index
})
}
I need to change foo value to index value. So the result should look like
myArray = [{'id':'73','foo': 1},{'id':'45','foo': 0}]
I think with this, I do get the current value, but the syntax is wrong to change its value:
myArray.find(x => x.id === '45').foo = 'new'
I do get the error Uncaught TypeError: Cannot assign to read only property 'foo' of object '#<Object>'

You can use map to change the property you want:
const myArray = [{ id: '73', foo: 'bar' }, { id: '45', foo: 'new' }, { id: '46', foo: 'do not change me' }]
const ids = ['45', '73']
const newArr = myArray.map(item => {
if (ids.indexOf(item.id) !== -1) {
return {
...item,
foo: 'newFooValue'
}
}
return item
})
console.log(newArr)
It's also important to note that change object properties directly (such as myArray.find(x => x.id === '45').foo) is not a good practice though.
In case itemsis in you state, you can simple change it by:
this.setState(prevState => ({
items: prevState.items.map(/* same code as in the snippet above */),
}))

Related

How to update Object in Array dynamically in React state

I'm fairly new to ReactJS and wrote this function I want to use to update an object in my state. It seems unable to use the "name" param to update my object and I don't really get why. I tried to code it in template literals as well.
const handleAccountingChange = (newValue, name, id) => {
const newState = selected.map((obj) => {
if (obj.id === id) {
return { ...obj, name: newValue };
}
return obj;
});
setSelected(newState);
};
I get no error in the browser console, but it doesn't update my state either. Any idea would be appreciated. I spent 2 hours on google but didn't find anything.
When you call obj.property = 'aaa' you set property to aaa.
What you try to do is update the property contained by the variable name, what you code does is update the property name.
To update a property from a variable you need to use :
const property = 'name'
obj[property] = 'aaa'
equivalente to :
obj.name == 'aaa'
This code solves your probleme :
const handleAccountingChange = (newValue, name, id) => {
// For the exemple I declare selected here
const selected = [ {id: 1, test: 'aaa'}, {id: 2, test: 'bbb'} ];
const newState = selected.map((obj) => {
if (obj.id === id) {
let myObj = {...obj};
myObj[name] = newValue;
return myObj;
}
return obj;
});
return newState; // instead ou can use setSelected(newState)
};
console.log(handleAccountingChange('ccc', 'test', 1));
const handleAccountingChange = (newValue, name, id) => {
const newState = selected.map((obj) => {
if (obj.id === id) {
return { obj[name]= newValue};
}
return obj;
});
setSelected({...newState});
}

React comes back with an unexpected token error when I do a dynamic destructuring of my object

I'm updating an input field with an onChange event in React.
const handleUpdateText = (id, name, text) => {
const newItems = items.map( item => {
if (item.id === id) {
return {...item, [menuLang][name]:text } // <-- error here at ][
}
return item;
} )
setItems(newItems);
}
name and text and name attribute and value of target input field.
menuLang is a state variable (string, eg "en").
The items are something like this:
{
id: 1,
type: "menuitem",
isVisible: true,
en: {
name: "Salad",
desc: "Fresh, seasonal ingredients",
},
price: "10",
},
Without dynamic destructuring it work fine:
const newItem = {...item}
newItem[menuLang][name] = text;
return newItem;
// instead of: return {...item, [menuLang][name]:text }
Any ideas what's the mistake?
use computed property name
let item={id:1,type:"menuitem",isVisible:!0,en:{name:"Salad",desc:"Fresh, seasonal ingredients"},price:"10"};
console.log(item)
let name = 'name'
let text = "Orange"
let menuLang = 'en'
item = {...item,[menuLang]:{...item[menuLang],[name]:text}}
console.log(item)
I don't think there is a mistake here, but I also didn't think what you want to do is possible via destructuring - I think you have to go down the route of your second example here.
const items = [{id:12,name: 'dj' }];
const handleUpdateText = (id = 12, name='ann', text='sample') => {
const newItems = items.map((item) => {
const obj = {...item};
if (item.id === id) {
return {...obj, [menuLang]: {[name]:[text]}};
}
return obj;
});
console.log('item', newItems);
};```

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 })
}
}

ES6 - Looping through keys in object and then assigning to new object

On page load I am calling a metadata API which returns an object with the metadata type as the key name and an array of metadata objects as the value like so:
The metadata objects need to be simplified and altered to play nicely with my React input components (each object will be a separate checkbox or radio button with a value and label). I basically need to return the same format but with value and label keys instead of id, display_on, name etc. I am doing the following in my container component to alter the data ready for being passed via props to my form components. The issue is that metadataFormattedForInput always return an empty object.
function mapStateToProps(state, ownProps) {
const metadataFormattedForInput = {};
Object.keys(state.metadata).forEach((key) => {
const metadataOptions = [];
state.metadata[key].map(option => (
metadataOptions.push({ value: option.id, label: option.name })
));
return (
Object.assign({}, metadataFormattedForInput, {
[key]: metadataOptions,
})
);
});
return {
metadataOptions: metadataFormattedForInput,
};
}
The culprit is the way you're using Object.assign (you're adding the properties to an empty object that you just discard afterwards because it's returned in a forEach call). However, Object.assign is unnecessary:
function mapStateToProps(state, ownProps) {
const metadataFormattedForInput = {};
Object.keys(state.metadata).forEach(key => {
metadataFormattedForInput[key] = state.metadata[key].map(option =>
({ value: option.id, label: option.name })
);
});
return {
metadataOptions: metadataFormattedForInput,
};
}
const metadata = {
phaseofthegame: [{id: 1, name: 'foo'}],
theme: [{id: 2, name: 'bar'}]
};
console.log(
mapStateToProps({ metadata }).metadataOptions
);
If you want to keep using Object.assign, then you can combine it with Array.reduce:
function mapStateToProps(state, ownProps) {
const renameProps = (acc, key) => Object.assign(acc, {
[key]: state.metadata[key].map(option =>
({ value: option.id, label: option.name })
)
});
const metadataOptions =
Object.keys(state.metadata).reduce(renameProps, {});
return { metadataOptions };
}
Here are some of the issue with your code:
You are using forEach in the wrong way, forEach don't return any value. In case you want to return some value, use map.
Also, you are using map and using it like forEach i.e. you are not utilizing the returned value
function mapStateToProps(state, ownProps) {
let metadataFormattedForInput = Object.keys(state.metadata).reduce((metadataFormattedForInput, key) => {
let metadataOptions = state.metadata[key].map(option =>
({ value: option.id, label: option.name })
);
metadataFormattedForInput[key] = metadataOptions;
return metadataFormattedForInput;
},{});
return {
metadataOptions: metadataFormattedForInput,
};
}
As others have said, and some other issues:
Returning a value in a forEach callback has no effect
On the other hand, a map callback should have a returned value
Object.assign does not mutate the second argument, so metadataFormattedForInput is not modified at all
Here is a functional way to do this, moving the Object.assign call outside the map and building the output with the spread syntax:
function mapStateToProps(state, ownProps) {
return {
metadataOptions: Object.assign(...Object.keys(state.metadata).map(key => (
{ [key]: state.metadata[key].map(option => (
{ value: option.id, label: option.name }
))
}
)))
};
}
var state = {
metadata: {
phaseofthegame: [
{ id: 123, display_on: ['a', 'b'], name: 'Attacking' },
{ id: 456, display_on: ['c', 'd'], name: 'Transition to Attack' },
],
theme: [
{ id: 789, display_on: ['e', 'f'], name: 'Rondo' },
{ id: 101, display_on: ['g', 'h'], name: 'Defending Individually' },
]
}
};
state = mapStateToProps(state);
console.log(JSON.stringify(state, null, 2));
.as-console-wrapper { max-height: 100% !important; top: 0; }

Categories

Resources