I am working on someone else's code and trying to figure out how to render a list alphabetically in React. I didn't write any of this and have very little knowledge of React, so please bear with me.
The ul looks like this:
<ul className="prod-group__filter__options" ref={(options) => this.options = options}>
{
filter.dropdown.map((option, index) => (
this.renderOption(filter, option, index)
))
}
</ul>
and the renderOption function, which obviously renders the list items looks like this:
renderOption(filter, option, index) {
return (
<li className="prod-group__filter__option" key={index}>
<label className="prod-group__filter__option__label">
<input name={filter.name}
type="checkbox"
defaultValue={option.slug}
checked={option.checked}
onChange={this.optionChangeHandler} />
{option.label}
</label>
</li>
);
}
The value I am trying to alphabetize is option.slug which is coming from a json list. Can anyone help me get a bit closer to rendering this list alphabetically?
It looks like filter.dropdown is your array of options. This array is passed to the .map() method which then runs a renderOption method in the given order.
Hence, you should sort the filter.dropdown array in your ul component code just before calling .map() on it.
You will have to sort option alphabetically using plain javascript before calling filter.dropdown.map... on it. I would advice using lodash function _.sortBy(option, 'slug'); where option is an array of objects with a property called slug then you can pass the sorted result to your map function.
In case anyone is interested, the solution was to sort the list items before calling .map() on it as both macbem and finch suggested. I used const to create an "items" constructor that I could then pass into the ul further down:
const items = [].concat(this.props.options)
.sort((a, b) => {
const One = a.slug.toUpperCase();
const Two = b.slug.toUpperCase();
return (One < Two) ? -1 : (One > Two) ? 1 : 0;
})
.map((option, index) => this.renderOption(name, option, index));
return (
<div className={classes}>
<a className="prod-group__filter__dropdown"
ref={(trigger) => this.trigger = trigger}
onClick={this.triggerClickHandler}>
{label}
</a>
<ul className="prod-group__filter__options" ref={options => this.options = options}>
{ items }
</ul>
</div>
);
Related
I have a json file that looks like this:
"skills":[
{
"type":"Languages",
"skill":[
{
"name":"Python"
},
{
"name":"Java"
},
{
"name":"JavaScript"
},
{
"name":"HTML"
},
{
"name":"Bash"
},
{
"name":"MySQL"
}
]
},
{
"type": "Flavours",
"skill": [
{
"name":"Reactjs"
},
{
"name":"Angularjs"
},
{
"name":"Agile"
},
{
"name":"Waterfall"
},
{
"name":"Kanban"
}
]
},
{
"type": "Technologies",
"skill": [
{
"name":"Jira"
},
{
"name":" BitBucket"
},
{
"name":"Git"
}
]
}
]
},
And i am trying to render it using a nested mapping function:
var skills = this.props.data.skills.map((skills) =>{
var skill = skills.skill.map(function(skill){
return <li key={skill.name}>{skill}</li>
})
return <ul key={skills.type}>{skills}</ul>
})
However it says "Error: Objects are not valid as a React child (found: object with keys {name}). If you meant to render a collection of children, use an array instead."
So i tried it like this:
var skills = this.props.data.skills.map(function(skills){
var skillsArr = []
var skill = skills.skill.map(function(skill){
var skillArr = []
skillArr.push(<li key={skill.name}>{skill}</li>)
return <span>{skillArr}</span>
})
skillsArr.push(<div key={skills.type}><h3>{skills.type}</h3>
<ul>
{skill}
</ul>
</div>);
return <div>{skillsArr}</div>
})
But this too gives me the exact same error, i dont get what is wrong here because if i do a single mapping of just the skill types it works, it is only when i try to render the inner mapped items does this error occur and break my code
This is how i am calling it btw:
<div className="bars">
<ul className="skills">
{skills}
</ul>
</div>
If we are talking about using React, you should think more about how to organize your code in order to follow a proper component structure, that will let clear what you want to render and how to properly split your data and responsibilities.
Looking to your JSON, we have a set of "skills" that have skills inside it (let's call them "innerSkills").
We can easily split it into 3 components, let's think together:
We can have a List that will render all your Skills.
We can have a Skill that will be responsible for rendering each Skill data, inside it, we will need to render the InnerSkills, so let's split it to another component.
We have then InnerSkill, that will be responsible for rendering each innerSkill that we have for each skill.
So, long story short, what we have is:
List -> Skill -> InnerSkills
Great, now that we established the split, let's see how we can make each component responsible for rendering its data.
Let's say we want to simply call <List skills={data} />. Following this, we can then start on the list itself, which would look something like:
const List = ({ skills }) => (
<ul>
{skills.map((skill, i) => (
<Skill key={i} skill={skill} />
))}
</ul>
);
Now that we are looping through all Skills and calling the Skill component for rendering it, we can take a look at how Skill should look, since it will also need to loop through skill.
const Skill = ({ skill }) => (
<li>
<p>Type: {skill.type}</p>
<ul>
{skill.skill.map((innerSkill, i) => (
<InnerSkill key={i} innerSkill={innerSkill} />
))}
</ul>
</li>
);
Great. Now we already have the two loops you need to render all the data, so it's just missing the definition on how each InnerSkill should look like, and we can take a simplified approach and say we just want to render the name, so it could be something like:
const InnerSkill = ({ innerSkill }) => (
<li>
<p>Name: {innerSkill.name}</p>
</li>
);
To summarize this implementation, I made a simple code sandbox so you can See it live! and play around with the components.
I hope this clarifies your question and helps you to think better in the future on how you want to organize stuff, first check how to split, later how to render. Don't try to start rendering everything inside loops because it will get nasty.
There are two things in your code causing this error:
var skills = this.props.data.skills.map((skills) =>{
var skill = skills.skill.map(function(skill){
// should be <li key={skill.name}>{skill.name}</li>
return <li key={skill.name}>{skill}</li>
})
// should be <ul key={skills.type}>{skill}</ul>
return <ul key={skills.type}>{skills}</ul>
})
Assuming you want a single unordered list of all skill names, I'd suggest using the flatMap() function to re-write this as follows:
<div className="bars">
<ul className="skills">
{this.props.data.skills.flatMap((skillGroup) =>
skillGroup.skill.map((skill) => (
<li key={skill.name}>{skill.name}</li>
))
)}
</ul>
</div>
I am trying to render the following json response.
"tickets": {
"tickets": [
"A Nuisance in Kiribati",
"A Nuisance in Saint Lucia"
]
}
My attempt is as below.
const display4 = Object.keys(this.state.ticketsList).map(
(keyName, keyIndex) => {
return (
<div className="my-posts">
<li key={keyIndex}>{keyName}_{keyIndex}:</li>
<li key={keyIndex}>{this.state.ticketsList[keyName]}</li>
</div>
);
}
);
But it will display the tickets array in a single line without any separation even. I tried all most all the things on the internet. But nothing works yet. How can I fix this?
You are reading the raw array and displaying it - that's why it displays all in one line. this.state.ticketsList[keyName] is the array. So you need to iterate through it in an additional loop to display each item.
Try this:
const display4 = Object.values(this.state.ticketsList).map(
tickets => tickets.map((ticketName, index) =>
<div className="my-posts">
<li key={index}>{ticketName}_{index}:</li>
<li key={index}>{ticketName}</li>
</div>
)
);
I'm struggling with reactjs for no reason. I'm a little confused about the magic behind and I'm not able to perform a simple operation of adding object / removing object from an array and display it.
I my parent, I have a method which on click append a new element:
appendNewPma(){
var newPma = this.state.pma.slice();
newPma.push(PmaType1);
this.setState({pma:newPma})
}
then my render method is like that:
render() {
return (
<div>
<a className="waves-effect waves-light btn" onClick={this.appendNewPma}>new</a>
{this.state.pma.map((Item, index) => (
<Item
key = {index}
ref = {"pma" + index.toString()}
onDelete = {() => this.onDelete(index)}
title = {index}/>
))}
</div>
);
}
Append work fine, but my array doesn't contain an object to display but rather a magical function that I don't understand.
But when I try to delete an object:
onDelete(idx){
console.log(idx);
var pma = this.state.pma.slice();
pma.splice(idx, 1);
this.setState({pma:pma})
}
When I delete from the array, no matter what index I will remove, it will only remove the last object. I know my code is not ok, but I have no idea how you can render element for an array of object (here my array is list of function constructor).
It will work better if I could get a straight ref to my object. Of course, I tryed to removed from the ReactDom, but was complening I was not updating from the parent...
I just want a simple array push/pop pattern with update.
Thanks for your help
Try below code. hope so it solve your issue.
addToArray = (event) => {
this.state.pma.push({"name ": "xyz"});
this.setState(
this.state
)
}
removeFromArray =(index) => {
var updatedArr = this.state.pma.splice(index, 1);
this.setState({arr : updatedArr})
}
I have a filter on an array in the render function in a React component:
someArray.filter(item => {
if (item.name.includes(searchText))return true
}).map(item=>{
return <h1>{item.name}</h1>
});
How can I elegantly display some text along the lines of "No search results" when no items are being returned by the map function?
There are a few ways you can do this. You can use a ternary operator (and also shorten your callbacks):
const filtered = someArray.filter(item =>
item.name.includes(searchText)
);
//Then, in your JSX:
{
filtered.length > 0 ?
filtered.map((item, key) =>
<h1 key={key}>{item.name}</h1>
)
:
<h1>No search results</h1>
}
This checks if there are any filtered results. If so, it will map them to h1s that have the name of the item. If not, then it will simply render a single h1 with the text 'No search results'.
One possible way is, instead of putting this code directly inside JSX render method, put it inside a method and call that method from render.
Like this:
_filterItem(){
const arr = someArray.filter(item => item.name.includes(searchText))
if(!arr.length) return <div>No data found</div>;
return arr.map(item => <h1 key={/*some unique value*/}>{item.name}</h1>)
}
render(){
return(
<div>{this._filterItem()}</div>
)
}
Suggestion:
With filter and map you can use concise body of arrow function instead of block body, for more details check MDN Doc.
Short and concise:
someArray.map(({name}) => name.includes(searchText) && <h1>{name}</h1>)
I have a component that receives offers and want to set the highest on top. This is the code:
var Offerslist = React.createClass({
_renderOffers: function(key) {
var details = this.props[key];
// TODO: set order of highest offer descending
return (
<li key={key} className="offer-of-item">
€ {details.price}
<a className="remove-offer" onClick={this._removeOffer}>X</a>
</li>
)
},
render : function() {
return (
<ol className="list-of-offers-per-item">
{Object.keys(this.props).map(this._renderOffers)}
</ol>
)
}
});
I thought of populating an array with objects {price:123, offerid:'foo123'} and then use .sort by price. But I can't populate an array like this as it never iterates through all objects at once. Is there perhaps some built-in React way to do this?
data:
For a ordinary array you'd use the sort method. But since your offers is an object, not an array, you can sort the keys by looking up the price in the offers object like this:
Object.keys(this.props)
.sort((a, b) => ( this.props[a].price - this.props[b].price ))
.map( this._renderOffers)