mapping through object which is inside object in react - reactjs

I have roomFeature.js file inside which there is object named as roomFeature. Inside roomFeature there is another object named beds with childs "single: "1", double: "2",".
Here's what i am talking about:
const roomFeatures = {
roomFeature: {
bed: true,
sleep: {
single: "1",
double: "3",
},
}
}
how can i access "1" if the "sleep" is available?
I have tried:
{roomFeatures.roomFeature.sleep.map((data) => {
return (
<div>
<span>
<BsFillPersonFill />
</span>{" "}
sleeps {data.single}
</div>
);
})}

If you only want to access to "1" one time then you can directly use {roomFeatures.roomFeature.sleep.single}
If you are trying to loop through the sleep object, you should convert it to an array before you use map function.
let temp_sleep = Object(roomFeatures.roomFeature.sleep);
temp_sleep.keys().map(key => {
return (
<div>
<span>
<BsFillPersonFill />
</span>{" "}
sleeps {temp_sleep[key]}
</div>
);
})
The keys function will return an array of keys in the object, so you can use the map function on the array.

Related

Deleting nested array element from a react state variable, doesn't update the state value in UI but has the updated value

I am new with react and working on a project where I am using the following
const [skillSet, updateSkillSet] = useState([{
skillType : '',
skillDescr : [
{
skillName : '',
skillExp : ''
}
]
}])
when elements are added array looks like
[
{
"skillType": "fe",
"skillDescr": [
{
"skillName": "s1",
"skillExp": "3"
},
{
"skillName": "s2",
"skillExp": "3"
},
{
"skillName": "s3",
"skillExp": "2"
}
]
},
{
"skillType": "be",
"skillDescr": [
{
"skillName": "b1",
"skillExp": "2"
},
{
"skillName": "b2",
"skillExp": "2"
},
{
"skillName": "b3",
"skillExp": "2"
}
]
}
]
when I add an element to the skillDescr array it's working fine and it's rendering properly in the UI. But when I delete an element from skillDescr, the values that gets removed from the array is correct(the one which I am trying to delete) but in the UI the last element is being removed.
output(UI) after deleting s2,b2
array after deleting s2,b2
This is the code I am using to delete the element
function deleteSkill(e, index, index1){
e.preventDefault()
skillSet[index].skillDescr = skillSet[index].skillDescr.filter((_,i)=>i!=index1)
updateSkillSet(skillSet)
}
I have index, index1 as there are two arrays
I am using map to render the UI
here is the code
{
skillSet.map(({skillType, skillDescr},index)=>{
return(
<div key={index} className='skills__box'>
<div className='skill__type'>
<input placeholder='Skill set name' onChange={(e)=>{updateSkilltype(e,index)}}></input>
{
skillDescr.map(({skillName, skillExp},index1)=>{
return(
<div key={index1} className='skill__name'>
<input placeholder='Skill name' onChange={(e)=>{updateSkillname(e, index, index1)}} required></input>
<input placeholder='Experiece Level' onChange={(e)=>{updateSkillexp(e, index, index1)}} type='number' required></input>
<AiTwotoneDelete onClick={(e)=>{deleteSkill(e, index, index1)}}/>
</div>
)
})
}
<button onClick={(e)=>{addSkill(e,index)}} className='btn btn-primary'>Add skill</button>
</div>
</div>
)
})
}
So my problem here is the array is getting updated properly but the render that's happening after the state update is not correct
That is because React doesn't know which element got removed. You need to add a key prop to all elements that correspond with the data rendered inside the loop so React can compare properly. Always try to prevent indices as key's.
Using something unique and stable, like an ID, would be even better.
{
skillSet.map(({skillType, skillDescr},index)=>{
return(
<div key={skillType} className='skills__box'>
<div className='skill__type'>
<input placeholder='Skill set name' onChange={(e)=>{updateSkilltype(e,index)}}></input>
{
skillDescr.map(({skillName, skillExp},index1)=>{
return(
<div key={skillName} className='skill__name'>
<input placeholder='Skill name' onChange={(e)=>{updateSkillname(e, index, index1)}} required></input>
<input placeholder='Experiece Level' onChange={(e)=>{updateSkillexp(e, index, index1)}} type='number' required></input>
<AiTwotoneDelete onClick={(e)=>{deleteSkill(e, index, index1)}}/>
</div>
)
})
}
<button onClick={(e)=>{addSkill(e,index)}} className='btn btn-primary'>Add skill</button>
</div>
</div>
)
})
}
For your delete function, map through the state array and just return the element unless it is the item at the index you want. If it is the index, return a copy of the item with the update skillDescr array.
function deleteSkill(e, index, index1){
e.preventDefault()
updateSkillSet(prev=>
prev.map((item,i)=>{
if(i === index){
return {...item,
skillDescr:item.skillDescr.filter((_,j)=>j!==index1)
}
}
//not the index, just return the item
return item
})
)
}

map function not working properly in object React

I have an array that contains as follows. I have successfully mapped the data and put it into the elements. but there are some values I couldn't get correctly. I have added a console.log to figure out the data. data is correct in the array, I want to get the Seats ("A1,B1") in here <h5><small>{seatNos.seats}</small></h5> but nothing is displaying. appreciate any help.
Data array
"data": {
"userBookings": [
{
"transactionId": 6357604,
"totalAmount": 350,
"createdAt": "2021-08-05 02:16:48",
"movieName": "Mortal Kombat",
"theaterName": "XxX Cinema",
"showTime": "17:30",
"refcode": "0016048GIN210805I",
"bookedSeats": [
{
"seatType": "Comfert",
"seats": "A1,B1",
"childTickets": 1,
"totalTickets": 2,
"bookedDate": "2021-08-05"
}
]
}
]
},
code
<div className="col-xl-5 col-lg-5 col-md-7 col-sm-7 col-xs-9 col-6" style={{paddingLeft:25}}>
<h5><b>{bookingsData.movieName}</b></h5>
<h5>{bookingsData.theaterName}</h5>
<h5><small>{bookingsData.showTime}</small></h5>
{bookingsData.bookedSeats.map((seatNos) => {
console.log(seatNos.seats);
<h5><small>{seatNos.seats}</small></h5>
})}
{/* <h5><small>{bookingsData.bookedSeats.seats}</small></h5> */}
</div>
You need to return element in map, and set key for this element:
{bookingsData.bookedSeats.map((seatNos, index) => {
console.log(seatNos.seats);
return <h5 key={index}><small>{seatNos.seats}</small></h5>
})}
Your arrow function inside the .map() doesn't return a value. You need a return before the JSX:
{bookingsData.bookedSeats.map((seatNos) => {
console.log(seatNos.seats);
return <h5><small>{seatNos.seats}</small></h5>
})}
Or to use the implicit return arrow function syntax (Either round brackets, or no brackets: () => () or () => returnedValue)
{bookingsData.bookedSeats.map((seatNos) => <h5>
<small>{seatNos.seats}</small>
</h5>)}
This is because you forgot the return
array.map((item) => {
return (
<p>{item.value}</p>
)
})

Loop over Json object -- React

I have a API for which I would like to loop over to get all the value of its keys. But unfortunately Iam only getting the keys for it.
My code until now:
...
const image_link = JSON.stringify(image);
const parsed_link = JSON.parse(image_link);
console.log('1st link', parsed_link[0].Header) // gives "abc.jpg"
...
...
<div>
{
Object.keys(parsed_link).map((e, i) => {
console.log(parsed_link);
console.log(e); // gives integers like 0,1,2 etc...
console.log(i); // gives integers like 0,1,2 etc...
<img src={e} alt="something" width="300" height="200" />;
return null;
})
}
</div>
...
...
API looks like this:
"Header": [
{
"image": "abc.jpg"
},
{
"image": "xyz.jpg"
},
{
"image": "lmn.jpg"
}
]
Please suggest, where am I goin wrong.
Thanks in advance
If you want to loop through an array, you can use map function.
If you want to loop through an object, you can have to convert the object into an array using Object.keys(YOUR_OBJECT) (if you want to get the key) or Object.values(YOUR_OBJECT) (if you want to get the values) than use map function.
You can directly print the array of data into an array of views. You have to return a view like these:
YOUR_ARRAY.map((item, index) => (
<div>YOUR VIEW</div>
))
// or
YOUR_ARRAY.map((item, index) => {
return(
<div>YOUR VIEW</div>
)
})
Note: you can only return a single parent view inside the return value. If you want to return multiple views inside the return value, you have to wrap them inside a single <div></div> or <React.Fragment></React.Fragment> or <></> parent.
In your code, I saw you wrote parsed_link[0].Header. So I assume that the API returns something like this:
[
{
"Header": [
{
"image": "abc.jpg"
},
{
"image": "xyz.jpg"
},
{
"image": "lmn.jpg"
}
],
...
},
...
]
Here is my answer:
<div>
{
parsed_link[0]['Header'].map((item, index) => (
<img
key={index}
src={item}
alt='something'
width='300'
height='200'
/>
)
}
</div>

Why cannot use 'map or filter' in testing library with typescipt?

I cannot use to array method for testing library.
My test code is like below.
const ListTitle = fakeData.forEach((el) => {
el.filter((element: string | object) => (typeof element === "string" ? element : element?.props?.children)); **** filter method has a error ts.2339
});
Attached, I want to exclude only text in fakeData array.
fakeData is like below the code.
interface FakeDataProps {
id:number
titleChild:JSX.Element
}
const fakeData: FakeDataProps[] = [
{
id: 1,
titleChild: (
<>
party goes on <span style={{ fontWeight: 500 }}> The end</span>
</>
),
},
{
id: 2,
titleChild: (
<>
party goes on <span style={{ fontWeight: 500 }}>The end</span> Your time
is ...{" "}
</>
),
},
{
id: 3,
titleChild: (
<>
party goes on <span style={{ fontWeight: 500 }}>The end</span>
</>
)
}
];
I expected
[
"party goes on The end",
"party goes on The end Your time is ...",
"party goes on The end",
]
But, In my test code, There are error occured that has "Property 'filter' does not exist on type 'FakeDataProps'.ts(2339)"
What shall I do?
The typescript error is occurring because you're calling .filter() on an individual item. You should be calling it on the array of items like so:
const ListTitle = fakeData.filter((element: string | object) => (typeof element === "string" ? element : element?.props?.children));
There is no need for .forEach() in your example, just use .filter() as above.

React multiple input (array of inputs) not working

So I'm attempting to render multiple input fields with React.
Everything looks fine until I remove an item. Always the last item is being "removed". If you want to try my code, write "A" in input field 1, "B" in 2, "C" in 3 and remove "B". You'll notice that you have removed "C" instead.
I have tried both value and defaultValue for input to no avail. I have also tried giving a name to the input. I think I am missing a key point here.
Any recommendations?
var MultiInput = React.createClass({
getInitialState: function() {
value = this.props.value
// force at least one element
if (!value || value == '') {
value = [ null ]
}
return {
value: value
}
},
getDefaultProps: function() {
return {
}
},
add_more: function() {
new_val = this.state.value.concat([])
new_val.push(null)
this.setState({ value: new_val })
},
remove_item: function(e, i) {
new_state = this.state.value.concat([])
new_state.splice(i,1)
this.setState({ value: new_state })
},
render: function() {
me = this
// console.log(this.state.value)
lines = this.state.value.map( function(e, i) {
return (
<div key={i}>
<input value={e} />
<button onClick={me.remove_item} >X</button>
</div>
)
})
return (
<div>
{lines}
<button onClick={this.add_more}>Add More</button>
</div>
)
}
})
There are a few things going on here.
To start, you shouldn't use the array index as the key when rendering in an array:
lines = this.state.value.map( function(e, i) {
return (
<div key={i}>
<input value={e} />
<button onClick={me.remove_item} >X</button>
</div>
)
})
The first time through, ["A", "B", "C"] renders:
<div key={0}>
...
</div>
<div key={1}>
...
</div>
<div key={2}>
...
</div>
Then, the second time, once you've removed "B" and left ["A", "C"], it renders the following:
<div key={0}>
...
</div>
<div key={1}>
...
</div>
So, when you removed item at index 1, the item previous at index 2 moves to index 1. You'll want to use some unique value that doesn't change when the position in the array changes.
Second, you should use the empty string instead of null for initialization, and then you'll see that you can't type anything in your inputs. That's because value ensures that an input's value is always whatever you pass it; you'd have to attach an onChange handler to allow the value to be edited.
Changing to defaultValue allows you to type in the box, but when you type, the string in this.state.value doesn't get updated--you'd still need an onChange handler.
Finally, your button has an onClick of this.remove_item, but your remove_item method seems to take the event and index as parameters. However, React will not pass the current index to remove_item; you would need to create a new function that passes the correct params:
onClick={me.remove_item.bind(null, i)}
That said, you really shouldn't call Function#bind inside render as you'll create new functions every time it runs.
Working Code
#BinaryMuse clearly explains why my code above doesn't work: by removing an item from the array and render is called again, the items change position and apparently React's algorithm picks the "wrong changes" because the key we're providing has changed.
I think the simplest way around this is to not remove the item from the array but rather replace it with undefined. The array would keep growing with this solution but I don't think the number of actions would slow this down too much, especially that generating a unique id that doesn't change might involve storing this ID as well.
Here's the working code: (If you wish to optimize it, please check #BinaryMuse's suggestions in the accepted answer. My MultInput uses a custom Input component that is too large to paste here =) )
var MultiInput = React.createClass({
getInitialState: function() {
value = this.props.value
if (!value || value == '') {
value = [ '' ]
}
return {
value: value
}
},
getDefaultProps: function() {
return {
}
},
add_more: function() {
new_val = this.state.value.concat([])
new_val.push('')
this.setState({ value: new_val })
},
remove_item: function(i,e) {
new_state = this.state.value.concat([])
new_state[i] = undefined
this.setState({ value: new_state })
},
render: function() {
me = this
lines = this.state.value.map( function(e, i) {
if (e == undefined) {
return null
}
return (
<div key={i}>
<input defaultValue={e} />
<button onClick={me.remove_item.bind(null, i)} >X</button>
</div>
)
}).filter( function(e) {
return e != undefined
})
return (
<div>
{lines}
<button onClick={this.add_more}>Add More</button>
</div>
)
}
})

Resources