I have a list of elements inside my react component, and I want them to be clickable. On click I call some external function passing item ID in arguments:
render () {
return (
<ul>
{this.props.items.map(item => (
<li key={item.id} onClick={() => {doSomething(item.id)}></li>
))}
</ul>
)
}
This code works, but it has a big performance drawback: a lot of new anonymous functions are being created on each call to render.
How can I pass that doSomething function as a reference here while still being able to provide a item.id to it?
You could use data-attributes, to set the correct id on each item while using the same function:
function doSomethingFromEvent(event){
return doSomething(event.target.dataset.id);
}
render () {
return (
<ul>
{this.props.items.map(item => (
<li key={item.id} data-id={item.id} onClick={doSomethingFromEvent}></li>
))}
</ul>
)
}
When setting data-* attributes in your element, you can get it back with dataset, in the form of a hash. For example, in doSomethingFromEvent I have event.target.dataset = {id: *id*}. See more on MDN
This is even cleaner when updating a hash (the state for example), with <li key={item.id} data-myattriute={myvalue} onClick={this.handleClick}></li>, I can simply define handleClick such as:
handleClick(event){
// Here event.target.dataset = {myattribute: myvalue}
Object.assign(myObject, event.target.dataset);
// or
this.setState(event.target.dataset);
}
Coming back to your problem, the great thing with this approach is that if you ensure your container element (ul) cannot be clicked outside its children with data-attributes (li), which is your case, you can declare the function on it:
render () {
return (
<ul onClick={doSomethingFromEvent}>
{this.props.items.map(item => (
<li key={item.id} data-id={item.id}></li>
))}
</ul>
)
}
Now your function is created a single time, and is not even repeated in each item.
What you can do is create a partially applied or higher order function to enclose the item.id and pass it along. So let's look at a toy example of this:
class App {
partiallyApplied = id => e => {
console.log(id,'this is passed in first')
console.log(e,'this is passed in second')
}
render(){
return (
<button onClick={this.partiallyApplied(1234)}>Click Me</button>
)
}
}
Now you have access to 1234 along with your event object
This is use transform-class-properties babel plugin. If do not or cannot use that, you can probably do something like this:
partiallyApplied(id){
return function(e){
console.log(id,'this is id')
console.log(e,'this is event')
}
}
but then you will have to bind this during your call and I just don't like that everywhere.
You could create a new component for every item in the array and use the props, like this:
class Li extends React.Component {
render() {
return <li onClick={this.onClick}> {this.props.children} </li>;
}
onClick = () => {
console.log(this.props.item);
};
}
class App extends React.Component {
state = {
items: [
{id: 1, name: 'one'},
{id: 2, name: 'two'},
{id: 3, name: 'three'},
]
};
render() {
return <ul>
{this.state.items.map(i =>
<Li key={i.id} item={i}>{i.name}</Li>
)}
</ul>;
}
}