3

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>
    )
  }
}
Jingles
  • 875
  • 1
  • 10
  • 31
  • InputText component has it's own local state, and the Form component has another state, when you are inputing something you need to trigger an event which updates the state from the form. https://stackoverflow.com/questions/38394015/how-to-pass-data-from-child-component-to-its-parent-in-reactjs – Nicolae Maties Jun 01 '20 at 10:37
  • Read [lift state up](https://reactjs.org/docs/lifting-state-up.html). – Bhojendra Rauniyar Jun 01 '20 at 10:40
  • By the way, this is not what you'd call "2 way data binding" and here's a good explanation - https://stackoverflow.com/a/13504965/5862900 – goto Jun 01 '20 at 10:47

3 Answers3

1

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>
    )
  }
}
Ishant Solanki
  • 205
  • 2
  • 13
1

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.

Deepesh
  • 6,138
  • 1
  • 24
  • 41
  • Thanks Deep. Is there anyway to make `this.handleKeyChange = this.keyUpHandler.bind(this);` and `keyUpHandler` function reusable for other form inputs as well? – Jingles Jun 01 '20 at 11:27
  • 1
    @Jingles I have updated the answer to make the function reusable. – Deepesh Jun 01 '20 at 11:34
0

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