We should avoid method binding inside render because during re-rendering it will create the new methods instead of using the old one, that will affect the performance.
So for the scenarios like this:
<input onChange = { this._handleChange.bind(this) } ...../>
We can bind _handleChange
method either in constructor:
this._handleChange = this._handleChange.bind(this);
Or we can use property initializer syntax:
_handleChange = () => {....}
Now lets consider the case where we want to pass some extra parameter, lets say in a simple todo app, onclick of item i need to delete the item from array, for that i need to pass either the item index or the todo name in each onClick method:
todos.map(el => <div key={el} onClick={this._deleteTodo.bind(this, el)}> {el} </div>)
For now just assume that todo names are unique.
As per DOC:
The problem with this syntax is that a different callback is created each time the component renders.
Question:
How to avoid this way of binding inside render method or what are the alternatives of this?
Kindly provide any reference or example, thanks.
If your environment supports arrow methods, you can use them for all methods. Use arrow functions within render . It's ok.
TL;DR: Binding callbacks is a JavaScript thing. It's necessary because you have to tell your callback what it's context is. Avoid binding by using the public class fields syntax, or bind your callbacks inside the constructor.
But when it comes to functional React, we can avoid using arrow functions as well in many cases, since they create a new function every time a component is re-rendered. We can do this by simply defining a function with the function keyword instead of using an arrow.
Using arrow functions or binding in JSX is a bad practice that hurts performance, because the function is recreated on each render. Whenever a function is created, the previous function is garbage collected. Rerendering many elements might create jank in animations.
Avoid arrow functions and binds in render. It breaks performance optimizations like shouldComponentUpdate and PureComponent. What Should I Do Instead? For contrast, here’s an example that doesn’t use an arrow function in render. In this example, index.js has no arrow function in render. Instead, the relevant data is passed down to User.js.
Arrow functions are reallocated on every render (same story with using bind). So although I’ve declared User.js as a PureComponent, the arrow function in User’s parent causes the User component to see a new function being sent in on props for all users. So every user re-renders when any delete button is clicked. ?
Here’s why: The parent component is passing down an arrow function on props. Arrow functions are reallocated on every render (same story with using bind). So although I’ve declared User.js as a PureComponent, the arrow function in User’s parent causes the User component to see a new function being sent in on props for all users.
Using arrow functions or binding in JSX is a bad practice that hurts performance, because the function is recreated on each render. Whenever a function is created, the previous function is garbage collected. Rerendering many elements might create jank in animations.
First: A simple solution will be to create a component for the content inside a map function and pass the values as props and when you call the function from the child component you can pass the value to the function passed down as props.
Parent
deleteTodo = (val) => { console.log(val) } todos.map(el => <MyComponent val={el} onClick={this.deleteTodo}/> )
MyComponent
class MyComponent extends React.Component { deleteTodo = () => { this.props.onClick(this.props.val); } render() { return <div onClick={this.deleteTodo}> {this.props.val} </div> } }
Sample snippet
class Parent extends React.Component { _deleteTodo = (val) => { console.log(val) } render() { var todos = ['a', 'b', 'c']; return ( <div>{todos.map(el => <MyComponent key={el} val={el} onClick={this._deleteTodo}/> )}</div> ) } } class MyComponent extends React.Component { _deleteTodo = () => { console.log('here'); this.props.onClick(this.props.val); } render() { return <div onClick={this._deleteTodo}> {this.props.val} </div> } } ReactDOM.render(<Parent/>, document.getElementById('app'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script> <div id="app"></div>
EDIT:
Second: The other approach to it would be to use memoize and return a function
constructor() { super(); this._deleteTodoListener = _.memoize( this._deleteTodo, (element) => { return element.hashCode(); } ) } _deleteTodo = (element) => { //delete handling here }
and using it like
todos.map(el => <div key={el} onClick={this._deleteTodoListener(el)}> {el} </div>)
P.S. However this is not a best solution and will still result in multiple functions being created but is still an improvement over the initial case.
Third: However a more appropriate solution to this will be to add an attribute
to the topmost div and get the value from event
like
_deleteTodo = (e) => { console.log(e.currentTarget.getAttribute('data-value')); } todos.map(el => <div key={el} data-value={el} onClick={this._deleteTodo}> {el} </div>)
However, in this case the attributes are converted to string using toString method and hence and object will be converted to [Object Object]
and and array like ["1" , "2", "3"]
as "1, 2, 3"
How to avoid this way of binding inside render method or what are the alternatives of this?
If you care about re-rendering then shouldComponentUpdate
and PureComponent
are your friends and they will help you optimize rendering.
You have to extract "Child" component from the "Parent" and pass always the same props and implement shouldComponentUpdate
or use PureComponent
. What we want is a case when we remove a child, other children shouldn't be re-rendered.
Example
import React, { Component, PureComponent } from 'react'; import { render } from 'react-dom'; class Product extends PureComponent { render() { const { id, name, onDelete } = this.props; console.log(`<Product id=${id} /> render()`); return ( <li> {id} - {name} <button onClick={() => onDelete(id)}>Delete</button> </li> ); } } class App extends Component { constructor(props) { super(props); this.state = { products: [ { id: 1, name: 'Foo' }, { id: 2, name: 'Bar' }, ], }; this.handleDelete = this.handleDelete.bind(this); } handleDelete(productId) { this.setState(prevState => ({ products: prevState.products.filter(product => product.id !== productId), })); } render() { console.log(`<App /> render()`); return ( <div> <h1>Products</h1> <ul> { this.state.products.map(product => ( <Product key={product.id} onDelete={this.handleDelete} {...product} /> )) } </ul> </div> ); } } render(<App />, document.getElementById('root'));
Demo: https://codesandbox.io/s/99nZGlyZ
Expected behaviour
<App /> render()
<Product id=1... render()
<Product id=2... render()
When we remove <Product id=2 ...
only <App />
is re-rendered.
To see those messages in demo, open the dev tools console.
The same technique is used and described in article: React is Slow, React is Fast: Optimizing React Apps in Practice by François Zaninotto.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With