Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Preferred way to bind methods in React Components

As of November 2017 I know of several ways of binding methods to React Components in order for the this keyword to point to the React Element that owns the method (necessary in event handlers for example)

1. Bind in constructor

class A extends React.Component {
  constructor(props) {
    super(props)
    this._eventHandler = this._eventHandler.bind(this)
  }

  _eventHandler() {
    // ...
  }

  render() {
    return <div onClick={this._eventHandler} />
  }
}

2. Arrow function in render()

class A extends React.Component {
  _eventHandler() {
    // ...
  }

  render() {
    return <div onClick={()=>{this._eventHandler()}} />
  }
}

3. bind in render()

class A extends React.Component {
  _eventHandler() {
    // ...
  }

  render() {
    return <div onClick={this._eventHandler.bind(this)} />
  }
}

4. ES2015 arrow function in class fields

class A extends React.Component {
  _eventHandler = () => {
    // ...
  }

  render() {
    return <div onClick={this._eventHandler} />
  }
}

5. @autobind decorator

class A extends React.Component {
  @autobind
  _eventHandler() {
    // ...
  }

  render() {
    return <div onClick={this._eventHandler} />
  }
}

1 is the safest way because it requires no build-time transformations by babel, but it very annoying to type.

2 and 3 have performance implications due to the binding happening on every render and the React diff algorithm

4 and 5 requires a lot less typing than 1, but they require support in babel and might not be part of the final specs yet. Besides that I am very against the idea of annotations (coming from a Java backend background I despise annotations because they are often overused and overly magical)

As of the latest Babel version is either 4 or 5 the recommended and safest (regarding future-compatibility) way of binding functions? Are there any other way I am not aware of? Should I keep using 1? Also if any of these considered safe to use are there any codemods that can change my codebase to use them?

Edit: @LucaFabbri pointed to reflective bind babel transform. It looks pretty cool, but it required a non-standard babel-plugin which I don't like because it is not very future-safe. I try to avoid build-time magic as much as possible, they are fine to use if you work on only one codebase over a long period of time, but if you maintain several codebases you need to handle the build-time magic each time (plus no support in create-react-app without ejecting).

like image 507
Hoffmann Avatar asked Nov 30 '17 09:11

Hoffmann


People also ask

What is method binding in React?

ReactJS bind() Method The bind() is an inbuilt method in React that is used to pass the data as an argument to the function of a class based component.

Why do we bind methods in React?

Binding methods helps ensure that the second snippet works the same way as the first one. With React, typically you only need to bind the methods you pass to other components. For example, <button onClick={this.

Why do we bind methods in constructor?

bind(something) returns a new function, in which references to this will refer to something . This is a way of saving the current value of this , which is in scope during the call to the constructor, so that it can be used later when the function is called.

What is the use of bind () in react?

The bind () is an inbuilt method in React that is used to pass the data as an argument to the function of a class based component.

Do React component class methods bind to the instance?

In the React docs it says the following [React Component Class] Methods follow the same semantics as regular ES6 classes, meaning that they don't automatically bind this to the instance. But clearly they do, as the second code example I've posted shows.

How do I bind the render and lifecycle methods in react?

With React, typically you only need to bind the methods you pass to other components. For example, <button onClick= {this.handleClick}> passes this.handleClick so you want to bind it. However, it is unnecessary to bind the render method or the lifecycle methods: we don’t pass them to other components. This post by Yehuda Katz explains ...

How do I bind a function to a specific context in JavaScript?

The third built-in Javascript method can do this. The .bind () method is similar to the other two in that you pass it the context you want to bind the function to, but it does not immediately run the function. Instead a copy of the function with the switched context is returned. This copy can then be run whenever you want.


1 Answers

If binding in the constructor (method 1) is too annoying for you, I'd say the preferred method would be an arrow function on the class field (method 4), as it's a simple babel transformation, it's a stage 3 proposal (basically future-proof), and avoids the performance concerns of methods 2 and 3 (if you ever hope to take advantage of shouldComponentUpdate or PureComponent.

One method that you may not be aware of is one that I came up with (haven't seen anyone else with anything similar) specifically for avoiding methods 2 and 3 when doing .map over some array of data to render out a list of components that need to be passed this.someInstanceMethod(withSomeArg) on props. For example:

class CatList extends React.Component {
  static propTypes = {
    kitties: PropTypes.arrayOf(PropTypes.instanceOf(Cat)),
  }

  adoptKitty(cat) {
    this.setState({ loading: true })
    return api.adopt(cat)
      .then(res => this.setState({ loading: false })
      .catch(err => this.setState({ loading: false, err })
  }

  render() {
    // ... other stuff ...

    {this.props.kitties.map(kitty => (
      <PureKittyCat 
        key={kitty.id} 
        // ... the problem:
        onClick={() => this.adoptKitty(kitty)}
      />
    ))}
  }
}

It's not immediately clear how to avoid passing a function literal on props within a .map like this, not only because you need to bind this, but you also need to pass the current element into the instance method. Most people in this situation would just give up the idea of making PureKittyCat a React.PureComponent.

My solution to this problem is to store a WeakMap on the Component instance to create a local cache (local to the parent component) that associates each kitty object with any methods I want to pass to the associated PureKittyCat component. It looks like this:

class CatList extends React.Component {
  static propTypes = {
    kitties: PropTypes.arrayOf(PropTypes.instanceOf(Cat)),
  }

  this.methodCache = new WeakMap()

  adoptKitty(cat) {
    this.setState({ loading: true })
    return api.adopt(cat)
      .then(res => this.setState({ loading: false })
      .catch(err => this.setState({ loading: false, err })
  }

  render() {
    // ... other stuff...

    {this.props.kitties.map(kitty => {

      // ... the good stuff:
      if ( !this.methodCache.has(kitty) ) {
        this.methodCache.set(kitty, {
          adopt: () => this.adoptKitty(kitty),
          // any other methods you might need
        })
      }

      // as long is this is the same in-memory kitty, onClick will
      // receive the same in-memory function object every render
      return (
        <PureKittyCat 
          key={kitty.id} 
          onClick={this.methodCache.get(kitty).adopt}
        />
      )
    })}
  }
}

WeakMaps are the right choice for something like this to avoid a memory-leak. And storing a new cache on the component rather than as a global cache (in its own module) avoids the possibility of running into name collisions if/when you try caching methods of the same name for the same object (here Cats) across multiple components.

The only other solution to this problem I've seen is explained here: https://medium.freecodecamp.org/why-arrow-functions-and-bind-in-reacts-render-are-problematic-f1c08b060e36.

Both involve some annoying setup, but that's really the only way to get achieve rendering optimizations for that scenario.

like image 136
Derrick Beining Avatar answered Oct 12 '22 08:10

Derrick Beining