Say you have the following TSX code:
render() {
const someParameter = 5;
return (
<div onClick={() => this.doSomething(someParameter)}></div>
);
}
Now, we know that we shouldn't pass fat arrow functions like this to the onClick handler because it will create a new function every time, forcing a rerender (this is especially bad when we're passing this callback to a deep ponent, not just a div).
So then the solution, if there were no parameters, would be to bind doSomething
to this
in the constructor of our class, like this.doSomething = this.doSomething.bind(this)
and then pass the bound function as the callback. However, this doesn't cut it in our case, because we want to pass a parameter to the function - someParameter. Imagine that someParameter wasn't just a stupid constant like it is above, but instead an element in an array that we get in a map() over an array in our render method. How do we handle this situation? That is, how do we pass a function that is not created from scratch every time, thereby breaking our ability to smartly rerender only when necessary?
Thanks!
Say you have the following TSX code:
render() {
const someParameter = 5;
return (
<div onClick={() => this.doSomething(someParameter)}></div>
);
}
Now, we know that we shouldn't pass fat arrow functions like this to the onClick handler because it will create a new function every time, forcing a rerender (this is especially bad when we're passing this callback to a deep ponent, not just a div).
So then the solution, if there were no parameters, would be to bind doSomething
to this
in the constructor of our class, like this.doSomething = this.doSomething.bind(this)
and then pass the bound function as the callback. However, this doesn't cut it in our case, because we want to pass a parameter to the function - someParameter. Imagine that someParameter wasn't just a stupid constant like it is above, but instead an element in an array that we get in a map() over an array in our render method. How do we handle this situation? That is, how do we pass a function that is not created from scratch every time, thereby breaking our ability to smartly rerender only when necessary?
Thanks!
Share Improve this question edited Jul 19, 2017 at 17:42 Aluan Haddad 31.9k10 gold badges83 silver badges95 bronze badges asked Jul 19, 2017 at 17:10 Michael TontchevMichael Tontchev 1,13914 silver badges27 bronze badges 3-
We really should benchmark our applications before determining
"that we shouldn't pass fat arrow functions"
in such contexts. Sure, it is suboptimal if render is called many times, but otherwise it is likely fine. It also reads very clearly and will be type-checked. – Aluan Haddad Commented Jul 19, 2017 at 17:49 - I agree with Aluan. If you're not having performance problems, you may not need to worry about this. Unless you're causing a massive cascade of re-rendering, you're probably fine in terms of putational cost, and you can worry about clarity and brevity instead. – salezica Commented Jul 19, 2017 at 18:49
- 1 It destroys a big part of the benefit of using Redux, though, especially if you're passing the callback at a high level. Also, my question wasn't "discuss the benefits of this performance optimization" - it was "how do we do this?" – Michael Tontchev Commented Jul 19, 2017 at 20:21
2 Answers
Reset to default 4bind
allows to bind not only context but also parameters. So you can do something like this in constructor:
this.doSomething = this.doSomething.bind(this, someArgument)
Above solution will work only if someArgument
doesn't change during ponent lifecycle.
If this argument is going to be dynamic the only solution is to use arrow function like in your example - but please note that might casue additional re-rendering only if this function is passed as a prop to child ponents (not HTML elements). According to react docs:
In most cases, this is fine. However, if this callback is passed as a prop to lower ponents, those ponents might do an extra re-rendering.
If you don't pass it to React child ponent it shouldn't be a problem and will not cause any additional re-rendering. Please note that there is a difference between React ponents and HTML tags. If you use arrow function as event callback on HTML element (e.g. on div generated in map) it will not trigger additional rendering of this div
because it's not a ponent and doesn't have render method. React Virtual DOM algorithm will decide if actuall DOM element corresponding to this div
should be updated and in our case existing DOM element will not be updated (you can check it using dev tools).
class Hello extends React.Component{
constructor() {
super();
this.state = {test: 0};
}
ponentDidMount() {
var i =0;
this.timer = setInterval(() => {
i++;
this.setState({test: i})
}, 1000)
}
ponentWillUnmount() {
clearInterval(this.timer)
}
render() {
return (<div>{this.state.test}
{
[1, 4, 6].map((v) => {
return (<div key={v} onClick={() => { console.log(this.state.test)}}>test</div>)
})
}
</div>);
}
};
ReactDOM.render(
<Hello name="World" />,
document.getElementById('container')
);
<script src="https://cdnjs.cloudflare./ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare./ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="container">
<!-- This element's contents will be replaced with your ponent. -->
</div>
You can inspect outoput generated by above code snippet to see that div
elements are not updated in DOM although they have arrow function defined as event callback and parent ponent it being re-rendred.
In case you need to pass this function to child ponent you can separetely pass prop with function and prop with argument value to avoid extre re-rendering (of course you also have to implement shouldComponentUpdate
):
<SomeComponent myClick={this.doSomething} myArgument={someArgument} />
and somewhere in SomeComponent:
//JSX in SomeComponent render method
<div onClick={() => this.props.myClick(this.props.myArgument)} >...</div>
You can also create additional method in SomeComponent instead of using an arrow function:
handleMyClick() {
this.props.myClick(this.props.myArgument);
}
and then use it JSX:
<div onClick={this.handleMyClick} >...</div>
So to sum up: if you use arrow function as event callback on HTML tags it shouldn't cause performance problems and it will not trigger any additional re-rendering - it will not break ability to smartly rerender only when necessary. It may cause additional re-rendering only if arrow function is passed down as prop to child ponents and in such case you can use soulution suggested by me.
Can you please check solution proposed here in "Protips" section? https://github./yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-no-bind.md
Looks as if this might work:
var List = createReactClass({
render() {
return (
<ul>
{this.props.items.map(item =>
<ListItem key={item.id} item={item} onItemClick={this.props.onItemClick} />
)}
</ul>
);
}
});
var ListItem = createReactClass({
render() {
return (
<li onClick={this._onClick}>
...
</li>
);
},
_onClick() {
this.props.onItemClick(this.props.item.id);
}
});