Adding data to an existing array of objects through an onChange handler - javascript

I have an array of objects
[
{
"0": {
"title": "Jessica Simpson",
"id": "324352342323432",
"url": "image-url.jpg",
"colourScheme": "",
"type": "",
"enabledUnits": ""
},
"1": {
"title": "Bill Murray",
"id": "5qocDvdm9XETFz33p725IG",
"url": "image-url.jpg",
"colourScheme": "",
"type": "",
"enabledUnits": ""
},
}
]
I'm attempting to add update the colourScheme value inside the object via an onChange event handler.
OnChangeHandler
const createOnChangeHandler = (floorPlan: FloorPlan, property: 'colourScheme' | 'type') => (
e: React.ChangeEvent<HTMLInputElement>
) => {
console.log(( e.target.value ))
const itemList = floorPlans.concat();
const index = itemList.findIndex((i) => i.id === floorPlan.id);
itemList.splice(index, 1, {...floorPlans, [property]: e.target.value});
};
But it's being added outside the object. For example... notice "colourScheme": "Black" is outside.
{
"0": {
"title": "Angeline Espaze",
"id": "5qocDvdm9XETFz33p725IG",
"url": "image-url.jpg",
"colourScheme": "",
"type": "",
"enabledUnits": ""
},
"colourScheme": "Black"
}
]
Where i would like
[
{
"0": {
"title": "Angeline Espaze",
"id": "5qocDvdm9XETFz33p725IG",
"url": "image-url.jpg",
"colourScheme": "Black",
"type": "",
"enabledUnits": ""
},
}
]
I think the issue is with itemList.splice? inside the onChange

The problem is:
You're spreading the wrong thing into the new object, and
You're not calling a state setter.
I wouldn't use splice for this at all, you can do the object update while copying the array rather than afterward. Instead (see comments):
const createOnChangeHandler = (floorPlan: FloorPlan, property: 'colourScheme' | 'type') => (
e: React.ChangeEvent<HTMLInputElement>
) => {
const {value} = e.target;
// Call the state setter; because we're updating state based on existing state, it's
// best to use the callback version
setFloorPlans(floorPlans => {
// Return a new array with a replaced object
return floorPlans.map(entry => {
if (entry.id === floorPlan.id) {
// Create a replacement object
return {...entry, [property]: value};
}
return entry; // No change to this object
});
});
};
That's assuming you're using hooks. If you're using a class component, use this.setState instead:
const createOnChangeHandler = (floorPlan: FloorPlan, property: 'colourScheme' | 'type') => (
e: React.ChangeEvent<HTMLInputElement>
) => {
const {value} = e.target;
// Call the state setter; because we're updating state based on existing state, it's
// best to use the callback version
this.setState(({floorPlans}) => {
// Return a new array with a replaced object
return {floorPlans: floorPlans.map(entry => {
if (entry.id === floorPlan.id) {
// Create a replacement object
return {...entry, [property]: value};
}
return entry; // No change to this object
}});
});
};

Try below code.
const createOnChangeHandler = (floorPlan: FloorPlan, property: 'colourScheme' |
'type') => ( e: React.ChangeEvent<HTMLInputElement>) => {
let lclfloorPlans = [...floorPlans];
lclfloorPlans.forEach(item => {
for (var key in item) {
if (item[key].id === floorPlan.id) {
item[key][property] = e.target.value;
break;
}
}
});
setFloorPlans(lclfloorPlans); // assign to your state value
};

Related

Changing object value in object with nested arrays of objects (infinite scroll)

The original data (old) looks like this:
Object {
"pageParams": Array [
undefined,
],
"pages": Array [
Object {
"cursor": 0,
"items": Array [
Object {
"content": "This is a users post!",
"createdAt": "2022-09-03T02:37:10.287Z",
"id": "93d13314-630e-4948-94a4-f75677afa7ba",
"likeCount": 10,
"likedByUser": false,
...
Just need to toggle likedByUser on click. passing in (id,performerId) in flatlist onclick.
Trying to map like this but I don't believe it is returning the original structure for the infinite scroll. need to return old with one changed object.
const likeHandler = (id: string, performerId: string) => {
const LikePostMutation: LikePostInput = {
id: id,
performerProfileId: performerId,
}
mutateLikes.mutateAsync(LikePostMutation).then(() => {
context.setQueryData(['fan.performer.getPostsFeed', {}], (old) => {
if (!old) return old
return old.pages.map((item: { items: any[] }) =>{
item?.items.map((item) => {
if (item?.id === id) {
let newItem = {
...item,
likedByUser: !item.likedByUser,
}
return { newItem }
}
}),
}
)
})
})
}
The error message TypeError: undefined is not an object (evaluating 'o[Symbol.iterator]') is from flatlist. I think the error message coming from view:
This is the shape of logged data after mapping incorrectly: I need it to be in the pages array and in an outer object.
}
Object {
content": "This is a users post!",
"createdAt": "2022-09-03T02:37:10.287Z",
"id": "93d13314-630e-4948-94a4-f75677afa7ba",
"likeCount": 10,
"likedByUser": true,
...
open to any suggestions on how to get better at this.
when doing immutable updates to arrays, you need to map over each item and return the same structure. infinite query structure is nested so it gets a bit boilerplat-y, but this should work:
context.setQueryData(['fan.performer.getPostsFeed', {}], (old) => {
if (!old) return old
return {
...old,
pages: old.pages.map(page) => ({
...page,
items: page.items.map((item) => {
if (item?.id === id) {
return {
...item,
likedByUser: !item.likedByUser,
}
return item
}
})
})
}
})
if you don't like the deep spreading, take a look at immer

How to create dynamic objects inside dynamic objects

This is created another objects dynamically when you click a button. But I want to create new object inside questions Array when I click a button.
const [inputFields, setInputFields] = useState([
{
sectionName: "",
sectionDesc: "",
questions: [{ questionType: "", questionText: "" }],
},
]);
const handleChange = (index: any, event: any) => {
const values = [...inputFields];
// #ts-ignore
values[index][event.target.name] = event.target.value;
setInputFields(values);
console.log(index, event.target.name);
};
const handleAddFields = () => {
setInputFields([
...inputFields,
{
sectionName: "",
sectionDesc: "",
questions: [{ questionType: "", questionText: "" }],
},
]);
};
First off, you are mutating state in your handleChange handler. You can avoid this using map()
const handleChange = (index: number, event: React.ChangeEvent<HTMLInputElement>) => {
setInputFields(prev => prev.map((p, i) => (
i === index
? {
...p,
[event.target.name]: event.target.value
}
: p
)));
};
As for adding to the questions array, you will need an index for which object you want to add to, then use the same technique as above to spread into that object.
const handleAddFields = (index: number) => {
setInputFields(prev => prev.map((p, i) => (
i === index
? {
...p,
questions: [...p.questions, { questionType: "", questionText: "" }]
}
: p)
));
};
Note: Using any for all parameters defeats the purpose of typing, try to use relevant types to take advantage of the type system.

How to render key value pairs from object react

This is object data that I have stored in my this.state.profile from an API request.
What I need to do know is render the values from the keys to the web broswer. I am trying with the code below which does not work. Also how do I render the objects within side this object? This is all so confusing :(
{
"localizedLastName": "King",
"lastName": {
"localized": {
"en_US": "King"
},
"preferredLocale": {
"country": "US",
"language": "en"
}
},
"firstName": {
"localized": {
"en_US": "Benn"
},
"preferredLocale": {
"country": "US",
"language": "en"
}
},
"profilePicture": {
"displayImage": "urn:li:digitalmediaAsset:C5603AQGjLGZPOyRBBA"
},
"id": "fm0B3D6y3I",
"localizedFirstName": "Benn"
}
How I am trying to render it:
const { profile } = this.state;
const profileList = Object.keys(profile).map((key,value)=>{
return (
<div>{key}{value.toString()}</div>
);
})
{ profileList }
try:
return (
{Object.entries(profile).map(([key,value]) => {
<div>{key} : {value.toString()}</div>
})}
)
the iteration needs to happen inside of the return.
You could build up your object outside your render call like below and just render it (elements).
var elements = [];
for (var prop in this.state.profile) {
elements.push(<div>{prop} : {this.state.profile[prop].toString()}</div>)
}
If it's not working my guess would be your state isn't initialised or your target js version doesn't support Object.entries
First of all, you need to deal with nested objects:
{
...
"firstName": {
"localized": {
"en_US": "Benn"
},
"preferredLocale": {
"country": "US",
"language": "en"
}
}...
}
If you try to render the value on the key firstName, you will get a object as value, and React can't render objects as elements.
And if you call toString() on it, you will get [Object object] as value.
To solve this, you gonna need some recursion:
const objToString = obj => {
let result = [];
Object.keys(key => {
if(typeof obj[key] === 'object'){
let children = (<div>{key} : {objToString(obj[key])}</div>)
result.push(children)
} else
result.push(<div>{key} : {obj[key]}</div>)
})
}
...
const profileList = objToString(profile)
This should give you this something like:
...
<div>firstName:
<div>localized:
<div>en_US: Benn</div>
</div>
</div>
...

Changing key value of object without discarding rest of keys

I would like to replace spaces with underscores for a specific key in all incoming objects. It works, however the rest of the keys dissappear.
The objects:
{
"id": "235",
"reference": "AA",
"name": "Jake H",
},
{
"id": "668",
"reference": "TC",
"name": "Felix S",
}
Actual outcome:
["Jake_H", "Felix_S"]
Method:
import jsonResults from './results.json'
data() {
return {
results: [],
}
},
mounted() {
const filteredResults = jsonResults
// I have other incoming objects that do not have names.
.filter(result => result.name)
.map(
result => result.name.replace(' ', '_')
)
this.results = filteredResults
}
I expect just the key value to change but what happens is the rest of the object is discarded.
Expect
{
"id": "235",
"reference": "AA",
"name": "Jake_H",
}
Actual
["Jake_H"]
You're returning result.name.replace(...) due to the implicit return of the ES6 arrow function - return result after modifying it. Destructuring is useful here, as is spreading:
.map(({ name, ...r }) => ({ ...r, name: name.replace(/ /g, "_")));
Alternate way:
.map(result => {
result.name = result.name.replace(/ /g, "_");
return result;
});
You need to return other properties as well in the map method, together with the modified name property.
const filteredResults = jsonResults
.filter(result => result.name)
.map(
result => {
return {
...result,
name: result.name.replace(' ', '_')
}
}
)
You are returning the just the name in the map funciton:
result => result.name.replace(' ', '_')
So rather do:
result => { result.name = result.name.replace(' ', '_'); return result; }

Cannot convert undefined or null to object reactjs

I was wondering about how to replace the item if it already exists inside state array and I got a great solution
https://codesandbox.io/s/4xl24j7r69
It works fine but my problem is I can't add a new item if the item doesn't exist inside the array I got an error Cannot convert undefined or null to object
Like this:
add = () => {
let newUser1 = {
"userId": 1,
"id": 3, // this is new id which is doesn't exist in this.state.data
"title": "Two New",
"body": "new data"
}
this.setState(prevState => {
let newData = prevState.data;
let user = newData.find(d => d.id === newUser1.id);
Object.assign(user, newUser1);
return { data: newData };
})
};
You're not adding the newUser to the data on state.
add = () => {
let newUser1 = {
"userId": 1,
"id": 4,
"title": "Two New",
"body": "new data"
}
this.setState(prevState => {
const user = prevState.data.find(d => d.id === newUser1.id);
if (!!user) {
//Casting user to a boolean, if find did return a truthy value you add it to the data with map
Object.assign(user, newUser);
const newData = prevState.data.map(d => d.id === user.id ? user : d);
return { data: newData }
} else {
return { data: prevState.data.concat(newUser1)};
}
})
};

Categories