React incrementing variable within .map() function - javascript

I am mapping through an array, and I want my variable i to be used as a unique key for my Components, however I do not know how (or where) to increment it correctly, if I add a {i++} within the <Component> tags then it will display the value of i on screen, and if I instead add {this.function(i)} and place the i++ inside the function, it will call the function but the variable i will reinitiate to the value of 0 everytime, so the key value will not be unique. I need the value of i to be the key for the component and it has to be incremented by 1 everytime, does anyone know how I can achieve this? Also, as you can see in the code, when the component is clicked it will make a function call which will send the value of i of the clicked component as a parameter to the called function.
Code:
function(i) {
console.log(i)
}
render() {
var i = 0;
var {array} = this.state;
return (
<div className="App">
{array.map(item => (
<Component key={i} onClick={(e) => this.function(i, e)}>
<p>{item.name}</p>
</Component>
))}
</div>
);
}

The map function gets a second parameter which is the index of the element:
{array.map((item, i) => (
<Component key={i} onClick={(e) => this.function(i, e)}>
<p>{item.name}</p>
</Component>
)}
Be aware that if you intend to sort this array or change its contents at runtime, then using array index as a key can cause some mistakes, as sometimes an old component will be mistake for a new one. If it's just a static array though, then using index as a key shouldn't be a problem.

.map already offer the increment, just add a second variable to the callback
render() {
var {array} = this.state;
return (
<div className="App">
{array.map((item,i) => (
<Component key={i} onClick={(e) => this.function(i, e)}>
<p>{item.name}</p>
</Component>
))}
</div>
);
}

You could try array.map((x, Key) => console.log(key)); ..
In place of console.log you could add your code, it should work fine as per your requirement.

Related

State not correctly refreshed due to asynchronous calls

I'm new in React (and in Javascript in general...) and I struggle to manage filters correctly. I think the problem comes from the fact that the calls are asynchronous but I cannot figured it out.
I have an application where you have matches and pronostics. You can filter matchs with a search box, as well as with a calendar:
If I click on a day on the calendar, the list of match must be filtered, but not the calendar itself, otherwise I will not be able to click on any other day
If I use the searchbox, both the matchs and the calendar must be updated
Both are OK in the fact that the matches are correctly filtered according to the day and/or search box, but it is not refreshed correctly, and the values of the pronostics and the score are completely messy after filtering
I have these 3 functions:
handleSelectDay = (selectedDate) => {
this.setState({selectedDate},this.handleFilters)
}
handleSearch = (search) => {
this.setState({search},this.handleFilters)
}
handleFilters = () => {
console.log(this.state.selectedDate)
const filteredMatchsWithoutDate = this.state.matchs
.filter(match => {
const valArray = this.state.search.toLowerCase().split(' ');
let matchBool = true;
for(let i = 0; i < valArray.length; i++) {
if (
match.team_a.name.toLowerCase().includes(valArray[i])
|| match.team_b.name.toLowerCase().includes(valArray[i])
|| match.location.toLowerCase().includes(valArray[i])
|| match.type.toLowerCase().includes(valArray[i])
){
matchBool = true;
}else{
matchBool = false;
}
}
return matchBool
});
const filteredMatchs = filteredMatchsWithoutDate
.filter(match => {
if(this.state.selectedDate != null)
return isSameDay(parseISO(match.schedule),this.state.selectedDate)
return true
})
}
and my render function:
render() {
return (
<div className="App">
<main>
<Navigation />
<Container>
<Filters
onSearch={this.handleSearch}
onCheckbox={this.handleCheckbox}
/>
<Row>
<Col sm={0} md={0} lg={4} xl={4} className="col-xxl-3">
<Calendar
onSelectDay={this.handleSelectDay}
matchs={this.state.filteredMatchsWithoutDate}
/>
</Col>
<Col sm={12} md={12} lg={8} xl={8} className="col-xxl-9">
<Row>
{ this.state.filteredMatchs.map(match => <Match match={match} />) }
</Row>
</Col>
</Row>
</Container>
</main>
</div>
);
}
I think that the problem comes from this.state.filteredMatchs.map(match => <Match match={match} />) which is not correctly refreshed due to the state filter, but I cannot figure out how I could deal with it and the fact that handleSearch and handleCheckbox are already calling the handleFilters method ahead.
Thanks in advance for your help :)
The fastest fix would be adding a key which you can take from map function like:
this.state.filteredMatchs.map((match, index)=> <Match key={index} match={match} />)
So react knows that it has unique key and knows which one to use.
In real big application you shouldn't use index as a key. Key should be 100% unique so the best is to set it as ID or uuid etc.
It looks like this could be because you are not using key props.
<Match key={someUniqueValue} match={match} />
Without key props react cannot track changes in arrays mapped to elements correctly.
https://reactjs.org/docs/lists-and-keys.html
Edit/ PS. You should never use an array index to map these change and it defeats the purpose of key props - https://robinpokorny.medium.com/index-as-a-key-is-an-anti-pattern-e0349aece318
You need to update your state variable filteredMatchs in your handleFilters method. Your logic is there to filter the data but you are not updating the state. Adding the setState line at the end of your handleFilters method will make it work.
this.setState({filteredMatchs})
Also, as #jazz and #sowam suggested you should also add key attribute to your component.

React render quantity of components based on a numeric value

I want to pass in a value to a component and render multiple child components based on that value. e.g. if I pass in count={4} in props, then I want to render 4 <Icon/> components. If I pass in 5, I want to render 5, and so on.
At the moment, all I can think to do is to take the value and turn it into an array (i.e. do a for loop and push a placeholder element to an array with each iteration) and then do a map on that array. But that seems like overkill.
Is there a simple solution to this?
You can do it like this:
...
return(
Array.from({length: props.count}, () => <Icon />)
)
You're probably looking a way to map amount of children to be populated. Check this live example: https://snack.expo.io/#zvona/mapping-child-components
This is the core code from that example:
const Icon = ({index}) => (
<View><Text>{`Icon #${index + 1}`}</Text></View>
);
const Icons = ({count}) => (
Array.from({length: count}).map((_item, index) => <Icon index={index}/>)
)
const App = () => {
return (
<View style={styles.container}>
<Icons count={5} />
</View>
);
}
You can use a simple for loop for this
for(let i = 0; i < count ; i++){
return <Icon />
}
If this doesn't work, you can do the following. It's a bit modern es6 function. Try this..
{[...Array(count)].map((x, i) =>
<Icon key={i} />
)}

React.js: How to filter JSX element array on custom attribute?

I am starting with a simple array of JSX elements:
const jsxArray = dataItems.map(item => (
<div>
<Header>{item.title}</Header>
<Paragraph>{item.body}</Paragraph>
<Paragraph customAttribute={item.isActive} >{item.tags}</Paragraph>
</div>
))
Inside render, or rather return since I use functional components for everything now, I'd like to filter for JSX elements where the isActive attribute was tagged true.
return (
{jsxArray
.filter(jsxElement => // want to filter in JSX elements
// that are true for customAttribute keyed to `item.isActive`)
}
)
Is there any way to do it?
If there is not precisely a good way I am open to workarounds.
It is possible for me to simply filter the array at an earlier step. It would result in some extra code duplication though, since I would still need the array of unfiltered JSX elements elsewhere.
You don't filter the list after you render it. At that point it's just a tree of nodes that doesn't have much meaning anymore.
Instead you filter the items first, and then render only the items that pass your criteria.
const jsxArray = dataItems.filter(item => item.isActive).map(item => (
<div>
<h3>{item.title}</p>
<p>{item.body}</p>
<p customAttribute={item.isActive} >{item.tags}</p>
</div>
))
It is possible for me to simply filter the array at an earlier step. It would result in some extra code duplication though, since I would still need the array of unfiltered JSX elements elsewhere.
Not necessarily. When dealing with filtering like this myself I create two variables, one for the raw unfiltered list and one for the filtered items. Then whatever you're rendering can choose one or the other depending on its needs.
const [items, setItems] = useState([])
const filteredItems = items.filter(item => item.isActive)
return <>
<p>Total Items: ${items.length}</p>
<ItemList items={filteredItems} />
</>
Instead of accessing the jsx element properties (which I think it's either not possible or very difficult) I suggest you to act in this way:
Save the renderer function for items in an arrow function
const itemRenderer = item => (
<div>
<Header>{item.title}</Header>
<Paragraph>{item.body}</Paragraph>
<Paragraph customAttribute={item.isActive} >{item.tags}</Paragraph>
</div>
)
Save the filter function in an arrow function
const activeItems = item => item.isActive
Use them to filter and map
const jsxArray = dataItems.filter(activeItems).map(itemRenderer)
Use them to map only
const jsxArray = dataItems.filter(activeItems).map(itemRenderer)
Hope this helps!
Usually you would filter the plain data first and then render only the markup for the filtered elements as described in #Alex Wayne answer.
If you worry about duplication of the markup, that can be solved by extracting a component from it:
const Item = ({title, body, isActive, tags}) => (
<div>
<Header>{title}</Header>
<Paragraph>{body}</Paragraph>
<Paragraph customAttribute={isActive}>{tags}</Paragraph>
</div>
);
For rendering the filtered list you can then do:
{items.filter(item => item.isActive).map(item => <Item {...item} />)}
and for the unfiltered list:
{items.map(item => <Item {...item} />)}

Retrieve clicked array item on React Component

I have a Search component that outputs values from an array to a ResultItem child component, every child component has a button with a onClick property on it. I bound a function to the button to get the value of an array item that I clicked.
What I have working is that every time I clicked on every single ResultItem button I get values of 0,1,2,3,4,5,6 individually which is perfect but I dont need the array indexes I need the values of those indexes
class ResultItem extends Component {
constructor(props) {
super(props);
this.handleClick = this.handleClick
}
handleClick(index) {
// index = this.props.applications[0]
// index = this.props.applications.map(obj => obj.videoId[0])
console.log('this click', index)
}
render() {
// console.log ('myProps', this.props.applications[0]);
const {applications} = this.props;
return (
<div>
{
applications.map((app, k) => {
return (
<Card key={k} style={styles.appCard}>
<CardMedia style={styles.appMedia}>
<div>
<Drafts color={white} style={styles.iconer}/>
</div>
</CardMedia>
<CardTitle key={k} title={app.videoId} subtitle="Application"/>
{/* <div key={k}><h3>{app}</h3></div> */}
<CardText>
<div>
<div>Status:
<b>test</b>
</div>
</div>
</CardText>
<FloatingActionButton
style={styles.addButton}
backgroundColor={'#CC0000'}
onClick={this.handleClick.bind(this, k)}
>
<ContentAdd/>
</FloatingActionButton>
</Card>
)
})
}
</div>
);
}
}
What I've tried so far:
if I use:
index = this.props.applications[0]
I get the first value of the array on ALL buttons I click on and
If I use:
index = this.props.applications.map(obj => obj.videoId[0])
I get the first letter of every single item of the array inside another array on every click, Is there any way I can get the value of the element I've clicked on , if so how?
When you map over an array you provide a function where the first argument is the current item, and the second one is the current index (of that item).
someArray.map((currentItem, currentIndex) => /* do stuff */ )
If you just care about the item, then there is no need to involve the index. You could just pass the current item directly to handleClick.
render() {
const {applications} = this.props;
return (
<div>
{
applications.map((item) => {
<FloatingActionButton
style={styles.addButton}
backgroundColor={'#CC0000'}
onClick={ this.handleClick.bind(this, item) }
>
</Card>
)
})
}
</div>
);
handleClick would then deal directly with the item, not the index. If you still want to use indexes, perhaps because that's nice to have if you need to manipulate the array at some later stage, then the index (second argument), let's call it index, could be passed to handleClick as before, and you would use that index to find the relevant item with
const clickedItem = this.props.applications[index]
index = this.props.applications[index]
or
index = this.props.applications[index].videoid

List elements not rendering in React [duplicate]

This question already has answers here:
When should I use a return statement in ES6 arrow functions
(6 answers)
Closed 26 days ago.
This is my Sidebar component.
const Sidebar = React.createClass({
render(){
let props = this.props;
return(
<div className= 'row'>
<h2>All Rooms</h2>
<ul>
{props.rooms.map((room, i) => {
<li key={i}> {room.name} </li>
})}
</ul>
{props.addingRoom && <input ref='add' />}
</div>
);
}
})
This is where I render it populating one room.
ReactDOM.render(<App>
<Sidebar rooms={[ { name: 'First Room'} ]} addingRoom={true} />
</App>, document.getElementById('root'));
The contents inside the <ul></ul> tag don't render at all. Any idea what I could be missing.
You aren't returning anything from the map function, so it renders nothing inside the unordered list. In your arrow function, you do:
(room, i) => {
//statements
}
This doesn't return anything. Array.prototype.map requires a callback that returns a value to do anything. map transform elements to the array, mapping (executing) the callback on each element. The return value is what the value is in the corresponding position in the returned and mapped array.
You must return the list element in the map function like so:
<ul>
{props.rooms.map((room, i) => {
return <li key={i}> {room.name} </li>;
})}
</ul>
Now, the mapped array of list elements is rendered because the list element is returned.
To make this shorter, you may write it in the form (params) => value which is equivalent to the above, where value is the returned value, like so:
{props.room.map((room, i) => <li key={i}> {room.name} </li>)}

Categories

Resources