React child callback binding this without overriding the original this

Viewed 675
class Parent extends React.Component {

  constructor(props) {
    super(props)
    this.handleChildOnClick = this.handleChildOnClick.bind(this)
  }

  handleChildOnClick() {
    /* I would like to do something here that requires both Child and Parent.
     * But 'this' points to Parent because I binded it in Parent's constructor.
     */
  }

  render() {
    return(
    <Child
      onClick={this.handleChildOnClick}
    />)
  }

}

In the handleChildOnClick callback, I would like to call a Child's method to retrieve some data and update Parent's state accordingly.

If I bind Parent's this to handleChildOnClick in Parents constructor, then I lose reference to Child, thus unable to call Child's method. But if I don't bind it, then I'm unable to set Parent's state inside handleChildOnClick.

Is there a way to bind this while not overriding the original this ?

Edit:
My Child component is from a library so I can't modify it.

Edit:
I'm using react-google-maps library. The child component is GoogleMap, and I would like to store the new view bounds of GoogleMap in Parent's state (by calling getBounds() on GoogleMap) whenever the onBoundsChange callback of GoogleMap gets fired.

2 Answers

Desired this is not considered Child context. The function is passed as a callback, it's provided with some dynamic this. If this is Child component instance, that's just a coincidence.

The problem is specific to legacy or poorly designed libraries that don't embrace modern JS OOP and rely on arbitrary dynamic this instead of passing all necessary data as function arguments. A well-known example is D3 library.

A function cannot have two this. A popular workaround is self = this trick in conjunction with regular (not arrow) function. However, this becomes clumsy with class syntax because it cannot be placed outside constructor:

class Parent extends React.Component {
  constructor(props) {
    super(props)

    const self = this;
    this.handleChildOnClick = function handleChildOnClick() {
      // `self` is Parent instance
      // `this` is dynamic context
    };
  }

  render() {
    return(
    <Child
      onClick={this.handleChildOnClick}
    />)
  }
}

This reshuffle is inconvenient because this is usually expected to be class instance in JS OOP.

As explained in this related answer, a way to address this problem is to provide helper function that performs this trick internally and maps dynamic context to function parameter:

function contextWrapper(fn) {
    const self = this;

    return function (...args) {
        return fn.call(self, this, ...args);
    }
}

class Parent extends React.Component {
  constructor(props) {
    super(props)

    this.handleChildOnClick = contextWrapper(this.handleChildOnClick.bind(this));
  }

  handleChildOnClick(context) {
      // `this` is Parent instance
      // `context` parameter is dynamic context
    }
  }

  render() {
    return(
    <Child
      onClick={this.handleChildOnClick}
    />)
  }
}

You need to call handler in child component from its props and update parent's state:

class Parent extends React.Component {

  handleChildOnClick = (data) => {
    /* I would like to do something here that requires both Child and Parent.
     * But 'this' points to Parent because I binded it in Parent's constructor.
     */
  }

  render() {
    return(
    <Child
      handleChildOnClick={this.handleChildOnClick}
    />)
  }

}

Then in you child component:

class Child extends React.Component {

      handleChildOnClick = () => {
        // you can even send data to parent click handler
        this.props.handleChildOnClick(data); // pass any arguments 
      }

      render() {
        return(
        <button
          onClick={this.handleChildOnClick}
        />)
      }

    }

This is how it works in react's pne way binding. I hope this would be helpful

Related