React: 2 way binding props

Viewed 1347

How can I do a 2 way binding of a variable from the parent component (Form.js), such that changes occurred in the child component (InputText.js) will be updated in the parent component?

Expected result: typing values in the input in InputText.js will update the state of Form.js

In Form.js

render() {
    return (
      <div>
        <InputText
          title="Email"
          data={this.state.formInputs.email}
        />

        <div>
          Value: {this.state.formInputs.email} // <-- this no change
        </div>

      </div>
    )
  }

In InputText.js

export default class InputText extends React.Component {

  constructor(props) {
    super(props);
    this.state = props;
    this.handleKeyChange = this.keyUpHandler.bind(this);
  }

  keyUpHandler(e) {
    this.setState({
      data: e.target.value
    });
  }

  render() {
    return (
      <div>
        <label className="label">{this.state.title}</label>
        <input type="text" value={this.state.data} onChange={this.handleKeyChange} /> // <-- type something here
        value: ({this.state.data}) // <-- this changed
      </div>
    )
  }
}
3 Answers

You would need to lift state up to the parent

parent class would look something like

onChangeHandler(e) {
   this.setState({
      inputValue: e.target.value // you receive the value from the child to the parent here
   })
}
render() {
    return (
      <div>
        <InputText
          title="Email"
          onChange={this.onChangeHandler}
          value={this.state.inputValue}
        />

        <div>
          Value: {this.state.inputValue}
        </div>

      </div>
    )
  }

children class would look something like

export default class InputText extends React.Component {

  constructor(props) {
    super(props);
    this.state = props;
  }

  render() {
    return (
      <div>
        <label className="label">{this.state.title}</label>
        <input type="text" value={this.state.value} onChange={this.props.onChange} />
        value: ({this.state.value})
      </div>
    )
  }
}

You can manage state in the parent component itself instead of managing that on child like this (lifting state up):

In Form.js

constructor(props) {
  super(props);
  this.handleKeyChange = this.keyUpHandler.bind(this);
}


keyUpHandler(e) {
  const { formInputs } = this.state;
  formInputs.email = e.target.value
  this.setState({
    formInputs: formInputs
  });
}

render() {
    // destructuring
    const { email } = this.state.formInputs;
    return (
      <div>
        <InputText
          title="Email"
          data={email}
          changed={this.handleKeyChange}
        />

        <div>
          Value: {email}
        </div>

      </div>
    )
  }

In InputText.js

export default class InputText extends React.Component {
  render() {
    // destructuring
    const { title, data, changed } = this.props;
    return (
      <div>
        <label className="label">{title}</label>
        <input type="text" value={data} onChange={changed} />
        value: ({data})
      </div>
    )
  }
}

You can also make your InputText.js a functional component instead of class based component as it is stateless now.

Update: (How to reuse the handler method)

You can add another argument to the function which would return the attribute name like this:

keyUpHandler(e, attribute) {
  const { formInputs } = this.state;
  formInputs[attribute] = e.target.value
  this.setState({
    formInputs: formInputs
  });
}

And from your from you can send it like this:

<input type="text" value={data} onChange={ (event) => changed(event, 'email') } />

This assumes that you have different inputs for each form input or else you can pass that attribute name also from parent in props to the child and use it accordingly.

You can simply pass a callback from Form.js to InputText and then call that callback in InputText on handleKeyChange

Related