four-eyes
four-eyes

Reputation: 12394

Call function from children in React

Assume I have this component

class Foo extends React.Component {
    onClick = () => {
        alert('Yay');
        this.props.setWhatever()
    }

    render() {
        return (
            <Bar>
                <input 
                    type='radio'
                    checked={this.props.getWhatever}
                    onClick={() => this.onClick()} />
            </Bar>
        )
    }
}


class Bar extends React.Component {    
    render() {
        return (
            <label>
               {this.props.children}
            </label>
        )
    }
}

The this.onClick() is not executed. When I change onClick={() => this.onClick()} to onClick={this.onClick()} it works, but ends in an endless loop. Why is that?

Upvotes: 1

Views: 98

Answers (3)

sachin kalekar
sachin kalekar

Reputation: 536

Arrow function expressions are ill suited as methods, and they cannot be used as constructors. Arrow functions automatically binds this to the parent/outside components or the context where the function is defined/executed.

  • onClick={() => this.onClick()} - when the click happens this
    actually refers to the input button and not your component.

  • onClick={this.onClick()} - A closure is created and executed as soon as your component is rendered.

  • onClick={this.onClick} - A closure is created and executed when click has happened.

Please bind all your functions in constructor and avoid arrow functions.

constructor(props){this.onClick = this.onClick.bind(this)}
...
onClick() {...}
...
<input onClick={this.onClick} />

Please read more about arrow functions here https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions

Update: Avoid arrow functions in places where you would want to use this inside that arrow functions.

Upvotes: 1

ravibagul91
ravibagul91

Reputation: 20755

You should always use onChange event when working with checked attribute for radio type input. In your case you might have encounter with warning.

Warning: Failed prop type: You provided a `checked` prop to a form field without an `onChange` handler. This will render a read-only field. If the field should be mutable use `defaultChecked`. Otherwise, set either `onChange` or `readOnly`. 

In your Foo component do this,

<input
  type="radio"
  checked={this.props.getWhatever}
  onChange={this.onClick.bind(this)} //Bind "this" here or in contructor or use arrow function as you are using 
/>

Working Demo

Upvotes: 0

Dacre Denny
Dacre Denny

Reputation: 30360

By setting the <input /> element's onClick prop to onClick={this.onClick()}, this causes the onClick function defined in Foo to be called each time the <input /> element is rendered.

Calling Foo's onClick will in turn call this.props.setWhatever() which, as I understand from you question, causes the <Foo/> component to re-render. Re-rendering the Foo component triggers the cycle to repeat again, which causes the endless loop behavior that you're noticing.

By setting the onClick prop to onClick={() => this.onClick()}, you're instead (locally) declaring an arrow function, and passing that to the onClick prop (rather than calling it immediately as detailed above).

With this "arrow function approach", when the input element is clicked, that event triggers the function that you defined and passed to the onClick prop to be called as a side effect of that user event.

Hope that clarifies things :-)

Update

There are a few ways to pass extra arguments from an onClick handler. One simple approach would be:

{/* Inside render() */}
<input type='radio' checked={this.props.getWhatever} 
       onClick={(event) => this.onClick(event, 'some', 'other', 'arguments')} />

And then update your components onClick method:

onClick = (event, second, third, fourth) => {
    console.log(second, third, fourth); // 'some', 'other', 'arguments'
    this.props.setWhatever()
}

Upvotes: 2

Related Questions