Skip to content Skip to sidebar Skip to footer

Create Function That Can Pass A Parameter Without Making A New Component

My question is to do with the issue React has for binding functions in the render function. The following is not good practice: render() {

Solution 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)


Solution 2:

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!


Solution 3:

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' />

Solution 4:

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.


Post a Comment for "Create Function That Can Pass A Parameter Without Making A New Component"