4

What I'm trying to do is call a function defined in a parent element from its child, but bind this to the child calling the function, rather than the parent where the function runs.

Is there a way to do this and would this be an anti-pattern if so? Thank you.

Parent Function to Pass to Child

onSelect = (event => {
  // Some code where `this` is the scope of the child calling the function
  // Not the scope of the parent where the function is defined
}

Parent Render Function

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

Child Render Function

render() {
  return (
    <button onClick={this.props.onSelect.bind(this)} />
  )
}
T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
Slbox
  • 10,957
  • 15
  • 54
  • 106
  • It's hard to tell what you mean by this, please update your question with a **runnable** [mcve] demonstrating what you're trying to do (as close as you can, that is), using Stack Snippets (the `[<>]` toolbar button). Stack Snippets support React, including JSX; [here's how to do one](http://meta.stackoverflow.com/questions/338537/). (Whether it's an "anti-pattern" is off-topic here.) – T.J. Crowder Oct 28 '17 at 16:21
  • What do you mean by "element"? Can you post some example code? – Bergi Oct 28 '17 at 16:22
  • But the tools you'll probably use are [`Function#bind`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind) and/or [`Function#call`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/call)/[`Function#apply`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/apply). – T.J. Crowder Oct 28 '17 at 16:22
  • Possible duplicate of [React - Call parent method in child component](https://stackoverflow.com/questions/40109698/react-call-parent-method-in-child-component) – ganjim Oct 28 '17 at 16:24
  • I've added some very basic code to better outline what I mean. I think it should be enough. It is not a duplicate, and it is probably an antipattern I am guessing. – Slbox Oct 28 '17 at 16:29

2 Answers2

2

The problem is you're defining onSelect as an arrow function, so it closes over this rather than using the this it was called with. Just make it a method or non-arrow function:

class Parent extends React.Component {
  onSelect() {
    console.log(this.constructor.name);
    console.log(this.note);
  }
  render() {
    return <Child onSelect={this.onSelect} />;
  }
}
class Child extends React.Component {
  constructor(...args) {
    super(...args);
    this.note = "I'm the child";
  }
  render() {
    return (
      <button onClick={this.props.onSelect.bind(this)}>Click Me</button>
    );
  }
}

ReactDOM.render(
  <Parent />,
  document.getElementById("root")
);
<div id="root"></div>
<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>

But you might consider binding onSelect once rather than repeatedly (e.g., not in render), perhaps in Child's constructor. But it really only matters if render will get called a lot. E.g.:

class Parent extends React.Component {
  onSelect() {
    console.log(this.constructor.name);
    console.log(this.note);
  }
  render() {
    return <Child onSelect={this.onSelect} />;
  }
}
class Child extends React.Component {
  constructor(...args) {
    super(...args);
    this.note = "I'm the child";
    this.onSelect = this.props.onSelect.bind(this);
  }
  render() {
    return (
      <button onClick={this.onSelect}>Click Me</button>
    );
  }
}

ReactDOM.render(
  <Parent />,
  document.getElementById("root")
);
<div id="root"></div>
<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>
T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
  • Thanks very much TJ. I have taken your advice regarding performance, and your advice regarding `call` up above put me on track to solve my issue, which essentially achieves the same end as your example. Is what I'm doing here bad practice/an antipattern though? – Slbox Oct 28 '17 at 17:02
1

Is there a way to do this and would this be an anti-pattern if so?

  1. Pass a function (not an arrow function), and bind it in the constructor.

  2. It's an anti pattern because the parent needs to be aware of the inner working of the child, and this breaks encapsulation.

How do you do that:

Use a standard method, and not an arrow function:

onSelect(e) {  
  this.setState({
    selected: !!e.target.value
  });
}

Bind the method in the constructor:

constructor(props) {
  super(props);

  this.state = {
    selected: false
  };

  this.onSelect = this.props.onSelect.bind(this);
}

Working example:

const { Component } = React;

class Child extends Component {
  constructor(props) {
    super(props);
    
    this.state = {
      selected: false
    };
    
    this.onSelect = this.props.onSelect.bind(this);
  }

  render() {
    const {selected} = this.state;
  
    return (
      <div>
        <input onSelect={this.onSelect} defaultValue="I'm the text" />
        
        <div>{selected ? 'selected' : 'not selected'}</div>
      </div>
    );
  }
}

class Parent extends Component {
  onSelect(e) {  
    this.setState({
      selected: !!e.target.value
    });
  }
  
  render() {
    return (
      <Child onSelect={this.onSelect} />
     )
  }
}


ReactDOM.render(
  <Parent />,
  demo
);
<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="demo"></div>
Ori Drori
  • 183,571
  • 29
  • 224
  • 209
  • Thanks very much I did find a way to get this working which I'll post once I have wrapped up my work. Regarding the antipattern/breaking encapsulation, is this really an issue if the child is stateless? – Slbox Oct 28 '17 at 16:54
  • 1
    If it's stateless, it doesn't have `this`. I think that you're trying to bend a single solution to the problem, instead of thinking about good solutions for the problem. So, what is the problem? – Ori Drori Oct 28 '17 at 16:56
  • It's stateless, but not stateless functional, if I have my terminology correct. The issue I'm trying to solve is that I need to manage items both from controls on the item itself in a list, and from the parent in bulk. Specifically deleting items from a list. A delete button on the item, and an ability to delete multiple items from the parent level. I want to use the same code to do this to avoid duplication. In any event what I mean is I am not making use of state in the child, so would this be an issue? – Slbox Oct 28 '17 at 16:58
  • There is a very simple solution to your problem, that doesn't require binding a method from the parent to the current child. Can you open a new question with a definition of your problem (deleting a child from the child, and mass deletion from the parent)? – Ori Drori Oct 28 '17 at 17:02
  • I'm intrigued, but too pressed for time. This solution works well enough for me for now. I just want to be sure I'm not committing any grievous programming sins that will haunt me later. – Slbox Oct 28 '17 at 17:16
  • 1
    It doesn't look like a grievous sin, but it's a sin :) – Ori Drori Oct 28 '17 at 17:18