Cannot assign to read only property 'property' of object '#<Object>' - javascript

I have this problem.
With interface:
export interface IDevice {
id: string,
selected: boolean
}
creating instance by:
let newDevice: IDevice = {
id: uuid4(),
selected: false,
} as IDevice;
It gets added to array in recoil state, and in React used in a function where array has been retrieved with useRecoilState().
const [leftList, setLeftList] = React.useState<IDevice[]>([]);
Now it is being used in a handler for selecting the devices on a list control, here the error occurs:
...
leftList.map((item: IDevice) => {
if (item.id === event.dataItem.id) {
item.selected = !item.selected;
}
return item;
})
...
And I get the error: Cannot assign to read only property 'selected' of object '#'
Even cloning the array first by [...leftList] does not help.
I'm lost:-) Hope someone can spread light on this?

State shouldn't be modified directly, which is what you're currently doing now in your .map() method by updating the objects. TS is trying to tell you not to do this by making your objects read-only.
Instead, you can create a new object with all the properties from item (done using the spread syntax ...), along with a new overwritting property selected, which will use the negated version of the item's currently selected item if the id matches the event's data item id, or it'll keep the original selected value:
leftList.map((item: IDevice) => ({
...item,
selected: item.id === event.dataItem.id ? !item.selected : item.selected
}))
Cloning the array using the spread syntax ([...leftList]) will only do a shallow copy, and won't do a deep-copy of the objects within it, as a result, modifying the object references within .map() is still modifying the original state.
Or, instead of spreading the item object and creating a new object each time (which can hinder performance a little as pointed out by #3limin4t0r), you can instead only return a newly created object when you want to modify the selected property:
leftList.map((item: IDevice) => {
if (item.id !== event.dataItem.id) return item;
return {...item, selected: !item.selected};
});

Related

How can the index of an object within an array be preserved when modifying an object property while using the spread operator?

I am having a React useState-Variable that stores an Array of Objects
which are of this Question type:
type Question = {
id: number,
text: string,
otherProps: string,
...... (and so on)
}
Example of my useState
const [questions, setQuestions] = React.useState<Question[]>([{id: 1, text: "hello?", otherProps: "Lorem Ipsum"}])
The order of these Question objects in the useState-Variable Array matters, so my question is: How should the following function be changed so that the text of the Question is changed but the array index of the modified object is maintained/kept?
I am aware that currently I am first deleting the object and then placing a newly created on at the end, but I can't figure out another way right now.
function setQuestionTextById(id:number, text:string) {
if (!questions) return;
const question:Question|undefined = questions.find(x => x.id === id);
if (!question) return;
const newQuestion: Question = {
...question,
text,
};
const filteredQuestions = questions.filter(item => item.id !== id);
setQuestions([...filteredQuestions, newQuestion]);
}
You should use map on the array with a function that checks if the id is the one you want - if so it modifies it with the new text, otherwise leaves it as is.
This way, your whole function becomes:
function setQuestionTextById(id:number, text:string) {
const updatedQuestions = questions.map(question => question.id === id ? { ...question, text } : question);
setQuestions(updatedQuestions);
}
Which I find much more readable than the original, as well as preserving order as you say you need.
One further refinement would be to use the functional update form of setQuestions so it doesn't depend on the previous value of questions, but I'll leave that up to you - it may not matter, depending on how this is being used in your component.

Computed property getting called before prop initialization

In my Vue app, I am getting an array from map getters and I am trying to filter this array based on type which is a prop in a computed property. I checked both of them, both are strings, but the filter is not working properly, since I feel the computed property is called before the prop is initialized with value. Need some help with this?
all: 'mdm/all', // here 'mdm' indicates module name and 'all' is the state
prop: [type]
Inside computed, I have a property called
getData() {
const filteredData = this.all.filter(ele => ele.type === this.type.toLowerCase());
return filteredData.map(item => (
{name: item.name,
orderNo: item.order_no
});
}
In the above code, both ele.type and this.type seems to be strings with similar value (say 'expired') but the filteredData always happens to be an empty array.
Not sure what could be cause for this.
I would debug what .filter() is doing with a console log inside of it, for example:
this.all.filter(ele => {
console.log(`${ele.type} === ${this.type.toLowerCase()}?`, ele.type === this.type.toLowerCase());
return ele.type === this.type.toLowerCase();
})`
which will print out the result of ele.type === this.type.toLowerCase() every loop so you can verify what exactly is happening.

Object.keys vs Object.value - .keys mutates and .value does not. Why is this?

I am trying to change the values inside an object in Javascript, Object.values while running a forEach loop would seem to make the most sense to me but Object.keys (obj[key]) actually updated the value on the object. Here is my code to explain better.
This way below works as expected
const usersKey = { user1: 18273, user2: 92833, user3: 90315 }
Object.keys(usersKey).forEach((key) => {
usersKey[key] = usersKey[key]*2
})
console.log(usersKey)
This way below does not update the object
const usersValue = { user1: 18273, user2: 92833, user3: 90315 }
Object.values(usersValue).forEach((value) => {
value = value*2
})
console.log(usersValue)
Why is this? Shouldn't both ways produce identical results?
This behavior has nothing to do with Object.keys() and Object.values() as such. They both return arrays with copies of keys/ values and do not mutate the object itself.
You're just accessing different values in your programs. In the first program you are actually mutating the object itself, in the second program you only change the value which is a copy of the actual value. As it is a primitive value the change of value is not reflected in the original value.
If you had e.g. an object as a value, then Object.values() would return a copy of the reference to that Object and therefore (as the reference points to the same data in memory) any change would also be reflected in the original array.
// now the values are not simple primitive types but an object
const usersValue = { user1: { id: 18273 } , user2: { id: 92833 }, user3: { id: 90315 } }
Object.values(usersValue).forEach((value) => {
value.id = value.id*2
})
// changes are now reflected in the object
console.log(usersValue)
See this thread for more information on pass-by-reference and pass-by-value.

Cannot get properties of an object after using filter()

Im trying to use filter() to isolate an object from an array of objects, and then present that object's title in React.
The oneliner looks like this:
{Object.values(articles).filter(e => e.id==36)[0].title}
Now after filtering, i get an array of size 1, with the object i want:
{Object.values(articles).filter(e => e.id==36).map(e =>
console.log(e))}
Outputs:
> Object { id: 36, title: "Fine landskap i ny dokumentar", img:
> "/images/landscape2.jpg", date: "2019-10-31 11:49", author: "The
> Onion", ingress: "Heyo", content: "Helt fantastisk", category:
> "Kultur", rating: 2 }
My problem is, when i try to add a [0].title after the filter function, i get a TypeError saying:
TypeError: Object.values(...).filter(...)[0] is undefined
Which doesn't make sense either, because:
{Object.values(articles).filter(e => e.id==36).length}
Outputs 1.
When i call it with just the [0] though, i get a reasonable error.
{Object.values(articles).filter(e => e.id==36)[0]}
Outputs:
Error: Objects are not valid as a React child (found: object with keys
{id, title, img, date, author, ingress, content, category, rating}).
If you meant to render a collection of children, use an array instead.
The filter function has obviously given me an array with the object i want, but i cannot access any of the object's properties.
It's strange this doesn't work...
You can try find. It will return the object you looking for:
{Object.values(articles).find(a => a.id==36).title || 'fallback if article is not found'}
It’s because when you call with the first index [0] you access to the first object present in your array an try to render it. But you can’t render an object directly in React.
So I imagine you’re inside the render method of your component.
render () {
const filteredObjects = Object.values(articles).filter( article => article.id === 36)
return filteredObjects.map( article => {
<div>
<h1> {article.title} </h1>
</div>
})
}
But as I understand you want to get only one object the one which has id value of 36. So maybe you can consider to use .find() method instead.
const findObjectId = Object.values(articles).find( article => article.id === 36)
Of course you can one line this :
{ Object.values(articles).find( article => article.id === 36).title || ‘ No object found with this id ‘ }
Using find() will not return an array but the first object corresponding to the predicate.
I imagine your input is an object of objects but if I’m wrong feel free to give me your input structure so I can adapt my answer to your needs.

Using setState to dynamically populate an array of objects

I have an array of Objects in my props from my API but I need to modify the elements in this array of Objects and then pass it to a player on my application. I can successfully get my array of object but when I try to modify the elements and set the data into a state, The new state ends up having just the last item of my list. Here is my code,
if(musicitem.musics){
if(musicitem.musics.length === 0){
this.setState({musicLimited:true })
}
else{
return musicitem.musics.map((item, index)=>{
this.setState({
musics:[
...musics,
{
url:apiConstants.API_MUSIC_FILES+item.url,
cover:apiConstants.API_MUSIC_FILES+item.cover,
artist:item.artist,
}
]
})
});
}
}
I expect my state of musics to have an array of newly constructed objects but that is not the case. It ends up with just the last object that is expected to be in my array of objects. This means that, it has actually loop through the array of objects from props but the next State keeps overriding the previous state.
You should mention previous state's property(i.e. music) to get setState know that while using spread operator.
You can access previous state as passing first params to this.setState().
Example :
this.setState(prevState => ({ music : [...prevState.music, {artist:item.artist}] }))
Your code should be updated like this :
if(musicitem.musics){
if(musicitem.musics.length === 0){
this.setState({musicLimited:true })
}
else{
return musicitem.musics.map((item, index)=>{
this.setState(prevState => ({
musics:[
...prevState.musics, // use prevState
{
url:apiConstants.API_MUSIC_FILES+item.url,
cover:apiConstants.API_MUSIC_FILES+item.cover,
artist:item.artist,
}
]
}))
});
}
}

Categories

Resources