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

Pin-Yen
  • 319
  • 2
  • 7

2 Answers2

3

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}
    />)
  }
}
Estus Flask
  • 206,104
  • 70
  • 425
  • 565
  • Eventually I added a [ref](https://reactjs.org/docs/refs-and-the-dom.html) to the child component. Thanks for providing another method to deal with this problem. – Pin-Yen Sep 02 '18 at 13:10
  • You're welcome. Yes, a ref will work if `this` is specifically Child instance. I didn't check how the library works, but in regular click handlers the first parameter contains all necessary data, this could be the case too. You can check which arguments a handler receives. – Estus Flask Sep 02 '18 at 13:22
2

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

Sakhi Mansoor
  • 7,832
  • 5
  • 22
  • 37
  • Thanks for your solution. But the Child component is from a library so I cannot define new functions on it. Is there a solution that doesn't require modifying Child ? – Pin-Yen Sep 01 '18 at 08:18
  • why do you need two handlers then ? you can child handler and you have declared Child in parent component – Sakhi Mansoor Sep 01 '18 at 08:20
  • Can you make it more brief in your post and what library you're using – Sakhi Mansoor Sep 01 '18 at 08:21