2

My question is to do with the issue React has for binding functions in the render function.

The following is not good practice:

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

as each re render would add a new function to the page, eventually causing the browser to run out of memory.

The solution is to do this:

constructor() {
   this.callFunction = this.callFunction.bind(this);
}

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

The problem with this is when I want to pass a value into the function.

I know I can make the div a child component, and pass the parameter in through the callBack, but this does not seem sensible if the div is only being used once in the whole application. I accept I could make this work, but this is not the scope of this question.

I also know that this solution:

render() {
   <div onClick={() => this.callFunction.call(this, param)}/>
}

Is no better, as it is still creating a new function.

So my question is, how can I create a function that I can pass a parameter into without making a new component, and without binding a new funciton on each render?

Rob
  • 14,746
  • 28
  • 47
  • 65
Alex
  • 3,730
  • 9
  • 43
  • 94

4 Answers4

1

You can change the declaration of callFunction to be an arrow function, which implictly binds the scope, like so:

callFunction = () => { 
  console.log('hi');
};

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions

Then your original render function would work as expected!

AHB
  • 548
  • 2
  • 7
  • But how would I pass a parameter into this function – Alex Sep 15 '17 at 13:18
  • @Alex, like this `
    this.callFunction(param)}/>` or like this `
    `. Just add the argument in the function like `callFunction = (param) => {`...
    – Chris Sep 15 '17 at 13:25
  • 1
    @Chris I know this will pass the parameters into the function, but this will also cause the function to be bound to the page every time the page rerenders, which will eventually cause the browser to run out of memory. I did state that I was aware of this in the question, and asked for alternate means of doing this – Alex Sep 15 '17 at 13:29
1

You can't avoid creating a second component as you need to pass a function reference as an event handler, this will be executed by the browser when the event triggers.

So the problem is not the binding but the fact that you need to pass a reference, and references can't receive parameters.

EDIT
By the way, if you don't like the syntax and noise of binding or anonymous arrow functions you can use currying.
I posted an example in a different question if you find it interesting. this won't solve the problem though, it's just another approach to pass a new reference (which i find it to be the most terse)

Sagiv b.g
  • 30,379
  • 9
  • 68
  • 99
0

Use Side effect

Side effect is something that a function use that comes from outside but not as argument. Now this mechanism is majorly used in Redux/Flux where the entire state is stored in a Store and every component fetches their state from it.

class MyComponent extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      handlerProps: {
        onClick: { count: 0},
        onChange: { count: 0}
      }
    }
  }
  
  onClickHandler = () => {
    const state = this.state.handlerProps.onClick;
    console.log('onClick', state.count);
  }
  
  onChangeHandler = (value) => {
    const state = this.state.handlerProps.onChange;
    console.log('onClick', state.count);
    this.setState({value: value})
  }
  
  buttonClick = () => {
    const random = Math.ceil(Math.random()* 10) % 2;
    const handler = ['onClick', 'onChange'][random];
    const state = this.state.handlerProps;
    state[handler].count++;
    console.log('Changing for event: ', handler);
    this.setState({handlerProps: state});
  }
  
  render () {
    return (
      <div>
        <input onClick={this.onClickHandler} onChange={this.onChangeHandler} />
        <button onClick={ this.buttonClick }>Update Props</button>
      </div>
    )
  }
}

ReactDOM.render(<MyComponent/>, document.querySelector('.content'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.0.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.0.0/react-dom.min.js"></script>
<div class='content' />
Rajesh
  • 24,354
  • 5
  • 48
  • 79
0

The only way I know of is to create a new React Component which takes the value and the event handler as props.

This way, the handler as a function remains static, and since the value is passed down separately (in its own prop) you don't have any functions being re-instanciated. Because you don't bind anything nor create a new function each time.


Here's an example:

We have two buttons. The first one prints the current state variable value and the other increments it by one.

Normally, if we had done this with onClick={() => this.print(this.state.value)} we would get a new instance of this function, each time the MyApp component would re-render. In this case, it would re-render each time we increment the value with the setState() inside this.increment.

However, in this example, no new instance of this.print happens because we are only passing its reference to the button. In other words, no fat arrow and no binding.

In the <Button /> component, we have a <button> to which event handler we pass a reference to a function - just like we did in <MyApp />. However, here we know exactly what to pass to the function. As such, we have myHandler trigger this.props.handler(this.props.value).

class MyApp extends React.Component {
  
  constructor() {
    super();
    this.state = {
      value: 0
    };
  }
  
  print = (value) => {
    console.log(value);
  }

  increment = () => {
    // This will trigger a re-render, but none of the functions will be reinstanciated!
    this.setState((prevState) => ({value: prevState.value + 1}));
  }
 
  render() {
    // Note that both handlers below are passed just the references to functions. No "bind" and no fat arrow.
    return(
      <div>
        <Button handler={this.print} value={this.state.value}>Print</Button>
        <button onClick={this.increment}>Increment</button>
      </div>
    );
  }
}

class Button extends React.Component {
  
  // Clicking the button will trigger this function, which in turn triggers the function with the known argument - both of which were passed down via props.
  myHandler = () => this.props.handler(this.props.value);
  
  render() {
    // Note again that the handler below is just given the reference to a function. Again, not "bind" nor fat arrow.
    return(
      <button onClick={this.myHandler}>{this.props.children}</button>
    );
  }  
}
 
ReactDOM.render(<MyApp />, 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>

Though quite tedious, it is an effective solution. That being said, even if you do create a new function each time you render, the performance implications are minimal. From the official docs:

The problem with this syntax is that a different callback is created each time the LoggingButton renders. In most cases, this is fine.

Chris
  • 57,622
  • 19
  • 111
  • 137