Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to avoid bind or inline arrow functions inside render method

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.

like image 408
Mayank Shukla Avatar asked Jul 12 '17 09:07

Mayank Shukla


People also ask

Is it OK to use arrow functions in render methods?

If your environment supports arrow methods, you can use them for all methods. Use arrow functions within render . It's ok.

What's an alternative way to avoid having to bind to this in event callback methods?

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.

How do you avoid arrows in React?

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.

Why you shouldn't use inline arrow functions in JSX props?

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.

Should I use arrow functions in rendering?

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.

What is the use of arrow function in pure component?

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. ?

Why does the parent component pass arrow functions to the parent?

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.

Is it bad to use arrow functions in JSX?

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.


2 Answers

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"

like image 182
Shubham Khatri Avatar answered Oct 07 '22 05:10

Shubham Khatri


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.

  • render()

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.

like image 37
Dawid Karabin Avatar answered Oct 07 '22 06:10

Dawid Karabin