I have a .map() function on an array.
When I do a console.log(object) inside the .map it logs the object.
But when I do <li key={key}>{object.name}</li> it shows nothing.
Also not in the inspection tool.
Component:
<StyledImagesList>
{this.state.images.map((imageObject, key) => {
// <img src={imageObject.contentUrl} title={imageObject.name} />
<li key={key}>{imageObject.name}</li>
{console.log(imageObject)}
})}
</StyledImagesList>
StyledImagesList has no styling
export const StyledImagesList = styled.ul;
Any idea why my li 's won't be visible?
Returning the <li> element in your map() callback should resolve the issue:
<StyledImagesList>
{ this.state.images.map((imageObject, key) => {
// <img src={imageObject.contentUrl} title={imageObject.name} />
/* Logging would need to happen before the return */
console.log(imageObject);
/* Add return here */
return <li key={key}>{imageObject.name}</li>
}) }
</StyledImagesList>
If logging to console were not needed, you could achieve the same result with the more concise equivalent of:
<StyledImagesList>
{ this.state.images.map((imageObject, key) => <li key={key}>{imageObject.name}</li>) }
</StyledImagesList>
Return is missing. Either
this.state.images.map((imageObject, key) => {
return <li key={key}>{imageObject.name}</li>;
})
or
this.state.images.map((imageObject, key) => (
<li key={key}>{imageObject.name}</li>
))
Map() uses return in it's code block in order to push each elements to a NEW array. It doesn't touch the original one.
The code you do inside the loop does run in fact... It's just that you didn't got any thing in return to show.
Doing
return <li key={key}>{imageObject.name}</li>
will build you a new array with those html elements for you.
The map() method creates a new array with the results of calling a function for every array element.
The map() method calls the provided function once for each element in an array, in order.
Note: map() does not execute the function for array elements without values.
Note: map() does not change the original array.
Related
I have a filter options, which shows checkbox. So when click on each checkbox the value should be added to array if not exists and remove the value from array if already exists and the state should be updated. I have tried using below code and it is not working.
const [showFilter, setFilter] = useState([]);
useEffect(() => {
dispatch(fetchproducts(slug, sort, pageInitial+1, showFilter));
console.log(showFilter);
}, [showFilter]);
function filterClick (id, title) {
const index = showFilter.indexOf(id);
if (index > -1)
setFilter(showFilter.splice(index, 1));
else
setFilter(showFilter.concat(id));
}
return (
<ul style={{display: showMe.includes(index) ? "block" : "none"}}>
{item.items.map((single, index1) => (
<li key={index1}>
<label><input type="checkbox" name="checkbox" onClick={(e) => filterClick(e.target.value, item.title)} value={single.items_id}/> {single.items_value}</label>
</li>
))}
</ul>
)
In the above code, array insertion is working, but the splice is not working and the state is not updating.
How to alter my code to get the expected result.
You use useEffect. The useEffect's callback will be triggered when one of dependency is changed.
splice function changes array in place (ie mutates the array). In this case your array variable (showFilter) is not changed, therefore useEffect's callback will not be triggered.
Try using filter function instead:
setFilter(showFilter.filter(el=> el !== id));
Splice modifies the original array which is not considered a good practice in React. Please use slice or`filter.
Using slice your code would look like:
setFilter(showFilter.slice(index, index + 1))
actually no biggie but how would a computed property filter function look like that always returns the current array + 5 more elements?
more in detail:
Template:
<span class="box-content" v-for="item in activeItems" :key="item.id">
<img class="item" :src="item.filename" />
</span>
Script
data: function() {
return {
items: [],
limit: 1,
};
},
computed: {
activeItems: function() {
return this.items.filter( function(s) {
if(s.length > this.limit) {
return s;
}
});
// return this.limit ? this.items : this.items;
}
},
on page load , an axios post request gets an object of items, whose response is pushed into the items array which is empty upon component declaration.
so axios -> get object with items -> push into empty array.
now i want to display ,like, 5 items and make a show more button.
The problem now is, my activeItems function is invalid, it does not know "this.limit" and i doubt anyway that it returns the correct result as i just made it return itself and not a set of objects / arrays.
What I would do next is trying around with splice and slice, array copies and pushing elements into it until a certain condition is met but.. is there a better way ?
Thanks in advance
The filter function should be used to filter based on the internal values of an array. Say you have an array of objects with persons, and each Person as an age, then you could use the Array.prototype.filter function to filter based on that age of each entry.
The filter function therefore goes through every entry in your array and determines whether an item should be included or excluded.
If you, on the other hand, want to limit the amount of entries based on a maximum number of entries, I would suggest you use Array.prototype.slice, as you mentioned already.
Your computed function could be rewritten to:
activeItems: function() {
return this.items.slice(0, this.limit)
}
First, in your code, this.limit is undefined because this is referencing the anonymous function. If you want to access the component, you will better use arrow functions syntax.
Also, s references an element of your array, so s.length will be undefined too I guess...
Now, filter does not seem to be the best choice for your need. I'll go with slice instead. Somthing like:
computed: {
activeItems() {
return this.items.splice(0, this.limit)
}
}
Where limit is increased by 5 when you click the show more button.
Of course you could do it. You just missed some code on it. Here how you fix it
activeItems: function() {
let limit = this.limit
return this.items.filter( function(item, s) {
return s <= limit
});
}
If you don't mind using filter, here are some way to do it.
First : put condition in your for loop, this one
<span class="box-content" v-for="(item, index) in items" :key="item.id" v-if="index <= limit">
<img class="item" :src="item.filename" />
</span>
Second is to slice your array on you desired length, this one
<span class="box-content" v-for="(item, index) in items.slice(0, limit)" :key="item.id">
<img class="item" :src="item.filename" />
</span>
I have a Map object:
let dateJobMap = new Map();
for (let jobInArray of this.state.jobs) {
let deliveryDate: Date = new Date(jobInArray.DeliveryDate);
let deliveryDateString: string = deliveryDate.toLocaleDateString("en-US");
if (dateJobMap.has(deliveryDateString)) {
let jobsForDate: IDeliveryJob[] = dateJobMap.get(deliveryDateString);
jobsForDate.push(jobInArray);
}
else {
let jobsForDate: IDeliveryJob[] = [jobInArray];
dateJobMap.set(deliveryDateString, jobsForDate);
}
}
In my render method, I want to call a TruckJobComp object for each delivery job in the value's array to display it:
<div className={ styles.column }>
<p className={ styles.description }>{escape(this.props.description)}</p>
{
dateJobMap.forEach(function(jobsForDate, dateString) {
jobsForDate.map(job => (
<TruckJobComp job = { job } />
))
})
}
</div>
This seems like it should work but doesn't. It never creates a TruckJobComp. I do a .forEach iteration on my Map, and for each value's array, I use .map to get the individual job object to send to TruckJobComp object.
When I create a temp array to grab the jobs from the last loop:
let tempJobs: IDeliveryJob[];
and in the loop add in:
if (dateJobMap.has(deliveryDateString)) {
let jobsForDate: IDeliveryJob[] = dateJobMap.get(deliveryDateString);
jobsForDate.push(jobInArray);
tempJobs = jobsForDate;
}
and then use that array in the render:
<div className={ styles.column }>
<p className={ styles.description }>{escape(this.props.description)}</p>
{
tempJobs.map(job => (
<TruckJobComp job = { job }/>
))
}
</div>
It displays as expected.
I do have a warnings in Visual Studio Code:
Warning - tslint - ...\TruckDeliverySchedule.tsx(104,38): error no-function-expression: Use arrow function instead of function expression
I don't know enough to understand. Line 104 corresponds with:
dateJobMap.forEach(function(jobsForDate, dateString) {
I am very new to this so I'm not 100% sure how most of this works. Just trying to put pieces I've learned together to get things to work.
Second Edit:
{escape(this.props.description)}
{
[...dateJobMap.keys()].map(jobsForDate => // line 154
jobsForDate.map(job => (
<TruckJobComp job = { job } />
))
)
}
Produces error:
[09:06:56] Error - typescript - src\...\TruckDeliverySchedule.tsx(154,27): error TS2461: Type 'IterableIterator<any>' is not an array type.
dateJobMap.forEach(...) returns undefined, so it cannot be mapped to a collection of elements.
ES6 maps have forEach method for compatibility purposes (generally for..of is preferred to iterate over iterables) and don't have map method. A map should be converted to array first, then it could be mapped to an element. Since values aren't used, only keys need to be retrieved:
{
[...dateJobMap.keys()].map(jobsForDate =>
jobsForDate.map(job => (
<TruckJobComp job = { job } />
))
)
}
All this warning is saying is that instead of using the syntax function(jobsForDate, dateString) {} you should use the syntax (jobsForDate, dateString) => {}.
The reason could be the way this is scoped in arrow functions versus function expressions. See this post.
My guess as to the reason your first approach didn't work but your second one did is that forEach doesn't actually return an array, and if it did, calling map within forEach would return an array of arrays (but, again, it doesn't). Not sure how React would handle that, but React does know how to handle a single array, which is what your last approach returns.
I am building a test app to learn more about React and I have made an API call which gets a huge JSON object.
I was able to break this json into the parts that I need and now I have 10 arrays of 3 props each. I am able to send these 10 arrays in 3 props to another component, which needs to use these 3 props 10 times and render a div class Card each.
I can console.log(this.props) and it shows 10 different arrays with 3 props each,however, I cannot produce a same element 10 times.. I tried using map() but since my array is initially undefined, map() is not able to function properly either. Is there any thing in react like *ngFor in Angular ?
What is the best way to go about this?
*EDIT
Here's more code guys. Sorry still noobie here..
ERROR : this.props.map is not a function
return(
<div>
{this.props.map((data,i)=>{
return(
<li key={i}>{data.likes}</li>
);
*EDIT 2
Soo I tried running map function with an if condition but the code still breaks the very moment the condition gets true..
render() {
if(this.props.url !== undefined){
this.props.map((data,i) =>{
return <li key={i}>{data.likes}</li>
})
}
My state method is :
state = {
userId: undefined,
likes: undefined,
url: undefined
}
and im setting my values on each data stream as follows :
const pics = await fetch(`${base_url}?key=${api_key}&q=${query}
&img_type=photo&per_page=12`).then(response => {
return response.json();
})
pics.hits.map((data) =>{
return this.setState({
userId: data.user_id,
likes: data.likes,
url: data.webformatURL
})
})
this.props won't have map, it's not an array. It's an object with a property for each property passed to your component. For instance:
<YourComponent foo="bar"/>
...will have this.props.foo with the value "bar".
So if you're passing an array to your component, like this:
<YourComponent theArrayProperty={[{likes: 42},{likes:27}]} />
...then you need the name of that property:
return (
<div>
{this.props.theArrayProperty.map((data,i) => {
return (
<li key={i}>{data.likes}</li>
);
})}
</div>
);
Side note: You can use a concise arrow function for the map callback instead:
return (
<div>
{this.props.theArrayProperty.map((data,i) => <li key={i}>{data.likes}</li>)}
</div>
);
...and no need for the () if you put the opening tag on the line with return (you can't leave off the ( if it's on the next line, but you probably knew that):
return <div>
{this.props.theArrayProperty.map((data,i) => <li key={i}>{data.likes}</li>)}
</div>;
...but that's a matter of style.
With little information that you have provided, my guess is that code fails at map() when you try to use it with undefined value.
Try adding a conditional check to render
{props && props.map([RENDER CODE HERE])}
You can just make simple if statement to check if the array is not undefined, and then pass it to map function.
Another option is to set a defaultProps for an empty array.
MyComponent.defaultProps = {
arrProp: []
};
Issue: I can only render one iteration of my array.
My desired result of course is to get the entire length of array objects.
Adding [key] to my rendered object fields is the only method that gives me any output. Without declaring the key in this way, I get nothing
Child Component
...
const Potatoes = ({potatoes}) => {
const PotatoItems = potatoes.map((potato, key) => {
if ([potato] == ''){
return false
} else {
return (
<li key={key}>
<span>{potato[key].name}</span>
<span>{potato[key].flavor}</span>
</li>);
}
});
return (
<div>
<ul>
{PotatoItems}
</ul>
</div>
);
};
Parent Component
...
render () {
const potatoes = new Array(this.props.potatoes);
return (
<section style={divStyle}>
<Potatoes potatoes={potatoes} />
</section>
)
}
Simply removing new Array() from around the potatoes constant fixes your issue.
It seems like you may have created an unnecessary additional array.
Then you can remove those [key] references on your object in the child component and you should be good to go!
Does this fix your issue?