machineghost
machineghost

Reputation: 35730

Best Practice For Reading Form Data in React

I'm trying to do something in React that would be very simple in any other framework: I want to collect a bunch of values from a form.

Previously I did this sort of thing with a Backbone View, and it was very simple:

readFormValues: function() {
    this.data.foo = this.$('[name="foo"]').val();
    this.data.bar = this.$('[name="bar"]:checked').val();
});

But in React I can't seem to find an easy way to do that. It seems my only options are to ...

NOTE: Apologies for the formatting: code blocks and lists don't play well together :-(

  1. Completely bypass React and use the jQuery + e.target to access the form:

    handleSubmit: function(e) {

    var $form = $(e.target).parents('form:first');

    this.data.foo = $form.find('[name="foo"]);

    },

    render: function() {

    return <form onSubmit="handleSubmit"><input name="foo"/></form>;

    }

    That works, and is simple, but it feels like I'm bypassing React and using JQuery when I should just be using React.

  2. Provide callbacks to every form control:

    handleFooClick: function(e) {

    this.data.foo = event.target.value;

    },

    render: function() {

    return <form><input name="foo" onChange="handleFooChange"/></form>;

    }

    This appears to be the React/Flux way, but it feels like a crazy amount of unnecessary work. In my Backbone example I needed just one line per form control, but with this approach every last control I build has to have its own onChange handler (and I have to hook that handler up to every element as I render it).

    EDIT: One more disadvantage of this approach is that inside the callbacks this.props and this.state won't point to my form control's props/state (it will point to the input's props/state). This means that not only do I have to write a handler per input AND add that callback to every input as I render, but I also have to pass in my data object to every input!

  3. Use refs:

    handleSubmit: function(e) {

    this.state.data.foo = this.refs.foo.value;

    },

    render: function() {

    return <form><input ref="foo"/></form>;

    }

    This seems like a more sane solution, as I only need to add a "ref" attribute to every form control, and then I can read the data as easily as I could in Backbone. However, all the React documentation suggests that using refs that way is wrong (all of the examples using refs involve sending signals to the controls, eg. "focus on this input", not on reading data out of the controls).

I feel like there must be a "React-ive" way to access my form data that isn't needlessly complex, but I'm not seeing it because I don't understand React well enough. If any React expert could explain what I'm missing I would greatly appreciate it.

Upvotes: 7

Views: 3239

Answers (6)

Neil
Neil

Reputation: 968

The quickest and easiest way, using React's useState():

const Form = () => {
  const [fullName, setFullName] = React.useState("");
  const [is18, setOver18] = React.useState(false);

  return (
    <form>
      <input
        type="text"
        name="fullName"
        value={fullName}
        onChange={event => setFullName(event.target.value)}
      />

      <input
        type="checkbox"
        name="over18"
        checked={is18}
        onChange={() => setOver18(!is18)}
      />
    </form>
  );
};

Upvotes: 0

Vishnu Sharma
Vishnu Sharma

Reputation: 1383

You can read form data using only one function.

class SignUp extends Component {

constructor(){
    super();
    this.handleLogin = this.handleLogin.bind(this);
}

handleLogin(e){
    e.preventDefault();
    const formData = {};
    for(const field in this.refs){
        formData[field] = this.refs[field].value;
    }
    console.log('-->', formData);
}
render(){
    return (
         <form onSubmit={this.handleLogin} className="form-horizontal">
                    <div className="form-group text-left">
                            <label className="control-label col-sm-2">Name:</label>
                            <div className="col-sm-10">
                            <input ref="name" type="text" className="form-control" name="name" placeholder="Enter name" />
                            </div>
                        </div>
                        <div className="form-group text-left">
                            <label className="control-label col-sm-2">Email:</label>
                            <div className="col-sm-10">
                            <input ref="email" type="email" className="form-control" name="email" placeholder="Enter email" />
                            </div>
                        </div>
                        <div className="form-group text-left">
                            <label className="control-label col-sm-2">Password:</label>
                            <div className="col-sm-10"> 
                            <input ref="password" type="password" className="form-control" name="password" placeholder="Enter password" />
                            </div>
                        </div>
                        <div className="form-group text-left"> 
                            <div className="col-sm-offset-2 col-sm-10">
                            <button type="submit" className="btn btn-primary btn-block">Signup</button>
                            </div>
                        </div>
                        </form>
 )        
} }export default SignUp;

Upvotes: 0

danbahrami
danbahrami

Reputation: 1012

For a smaller project I have been using a single onChange call back for all inputs - something like this...

HandleClick(event) {
    let values = this.state.values;
    values[event.target.name] = event.target.value;
    this.setState({values});
}

This requires that you name your inputs the same as you name their state property but I quite like that. Then you give the value stored in state to the value attribute of your input and you're all set - all your form state stored in one place with a single handler function.

Of course there are more scalable ways - I was just reading about a framework for react called formsy that looked interesting. Here's a tutorial: http://christianalfoni.github.io/javascript/2014/10/22/nailing-that-validation-with-reactjs.html

Hope that helps

Dan

Upvotes: 1

Mark John De Asis
Mark John De Asis

Reputation: 11

This is how I handle all fields in a form.

            var My_Field=React.createClass({
                _onChange: function(evt) {
                    var e=this.props.parent_form;
                    if (e) {
                        e.setState({email: evt.target.value});
                    }
                },
                render: function() {
                    return (
                        <input type="text" name={this.props.name} placeholder={this.props.label} onChange={this._onChange}/>
                    );
                }
            });

            var My_Form=React.createClass({
                getInitialState: function() {
                    return {
                        email: "",
                    };
                },
                _onSubmit: function(evt) {
                    evt.preventDefault();
                    alert(this.state.email);
                },
                render: function() {
                    return (
                        <form onSubmit={this._onSubmit}>
                            <My_Field name="email" label="Email" parent_form={this}/>
                            <input type="submit" value="Submit"/>
                        </form>
                    );
                }
            });

Upvotes: 1

Rick Jolly
Rick Jolly

Reputation: 2999

First, jQuery is an unnecessary dependency and it's not the cleanest option so let's rule it out.

Next, refs have issues with flexibility. See this answer for details. Let's rule refs out for all but the simplest cases.

That leaves option #2 - what Facebook calls controlled components. Controlled components are great because they cover all use cases (like validation on keyup). Although it's not much code, if you'd rather not add a simple change handler for each form element, you might use one change handler for all elements with the use of bind. Something like this:

handleChange: function(fieldName, e) {
  console.log("field name", fieldName);
  console.log("field value", e.target.value);
  // Set state or use external state.
},

render: function() {
  var someValue = this.state.someValue; // Or a prop is using external state
  return (
    <div>
      <input 
        name="someName" 
        value={someValue} 
        onChange={this.handleChange.bind(this, "someName")} />
    </div>
  )
}

Or for an even cleaner way, see this answer.

Upvotes: 3

Carl Groner
Carl Groner

Reputation: 4359

You can use ReactLink to create a two-way binding between your react component and your state.

Described here: https://facebook.github.io/react/docs/two-way-binding-helpers.html

Upvotes: 1

Related Questions