Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Best practice for ReactJS form components

I am looking for a best practice to have a ReactJS component responsible for the form for users to edit a given entity. Very simplified example here. Actual forms would in many cases have several more fields and more GUI functionality.

React.createClass({     getInitialState: function() {         return {             entity: {                 property1: null,                 property2: null             }         };     },      handleChange: function(e) {         var entity = this.state.entity;          switch(e.target.name) {             case 'property1':                 entity.property1 = e.target.value;                 break;             case 'property2':                 entity.property2 = e.target.value;                 break;         }          this.setState({             entity: entity         });     },      render: function() {         return (             <div className="entity-form">                 <form onChange={this.handleChange}>                     <input type="text" name="property1" value={this.state.entity.property1} />                     <br />                      <textarea name="property2" value={this.state.entity.property2}></textarea>                     <br />                 </form>             </div>         );     } }); 

The fields of the form is directly editing an entity object, that could then be saved to a RESTful api. I want the component to be updated as the user change the fields, so the GUI could react based on the input during typing (like validations, info etc).

In theory, I could have the whole state object represent the entity that is being edited, so every property of the entity is first level state variables. However, I want to be able to add additional state variables for GUI functions and other things related to what the component is going to do, so I would prefer the entity object to be one state variable like the "entity" state variable above. The object could of course be some more complicated object, like a Backbone model or similar, but in this simplified example, I just use a simple object whit the required properties.

So, in search of the best practice way to make React components for this purpose, I have some questions:

  1. Props or state.

In this case, I have chosen to put the entity object with the content for the form in a state variable instead of prop. This is to be able to update the object during form input without having to call the parent and update the props. As far as my React experience goes, that would be the best practice for a form component like this.

  1. Controlled or uncontrolled inputs.

In the simplified example above, I use controlled inputs. This leads to updating the state and re-rendering the component on every change (like every character entered of a text field). Is this the best practice? The good thing is that the component has full control of what happens, instead of having defaultValue paramters, and on some event (like the user pressing a save button), the component extract the values, update the entity and save it to the server. Is there any reasons (or opinions) on if controlled or uncontrolled inputs should be used in cases like this?

  1. onChange for the form or every input

The example has an onChange on the form tag, and it causes the handleChange method to be called every time any of the fields in the form is changed. However, since the inputs are controlled (have value parameters), React complains that the input fields does not have an onChange property. Does this mean having a common onChange on the form tag is bad practice, and I should remove it and put onChange on every single field instead?

  1. Updating individual properties

In the above example, I use a switch based on what input field is being update (when handleChange is called). I guess I could instead make sure all field names is in sync with the property names of the entity, and I can set properties of the entity object in handleChange based on the name of the field from the event (e.target.name). However, this makes it hard to have individual needs per field, even if most fields just update an entity property directly. I guess an alternativ is a switch with a default block setting based on the name of the input, and case blocks for any field that require other ways of updating (like filtering the value before setting it on the entity). Please comment this if you know some much better way of handeling field updates this way.

  1. Updating the state entity

One big problem of this example, is the way the entity object is updated. Since the entity variable in the handleChange is set to the entity object from current state, this is just a pointer, and updating the entity variable will change the object in state. The React pages say you should never update state directly. One of the reasons is something I have experienced when updating the state this way before calling setState. If having a shouldComponentUpdate method, the prevState contain the new state, since the content of the prevState argument sent to the shouldComponentUpdate is based on what was in the state when setState was called. As far as I know, there is no simple way to clone a object in javascript. So the question is, when having whole objects that I need to update properties of (and not touching the other values in the object) instead of just running setState of a single state variable, what is the best way to do this without causing theese kinds of state mixups?

like image 557
henit Avatar asked Oct 29 '14 09:10

henit


People also ask

How small should your React components be?

Your component should be small With React components, the rules are a bit different, since JSX tends to take up more lines even for simple elements. 50 lines is a good rule of thumb for the body of your component (for class components, that is the render method).

Should I use Camelcase in React?

In React, all DOM properties and attributes (including event handlers) should be camelCased. For example, the HTML attribute tabindex corresponds to the attribute tabIndex in React. The exception is aria-* and data-* attributes, which should be lowercased.


1 Answers

  1. Anything that is going to change goes in State.
  2. If you're looking at loading an existing entity and editing it, you want controlled inputs, and you want to set the values accordingly. I tend to stay away from defaultValue in most cases (outside of dropdowns)
  3. This ties back in to your previous question. If you specify a value, you are using a controlled input, and you have to provide an onChange handler for any controlled input, otherwise it is set in stone. A benefit of controlled inputs is that users can't edit them, so if you had some properties locked down (maybe for read only, security reasons), when the user attempts to save, even if they edited the HTML directly, React should pull the property's value from the vDOM representation (could be wrong here, but I believe I've tested this before). Anyway, you have to have onChange set directly on controlled inputs. As far as using event delgation (at the form level), this isn't a bad practice at all for a lot of events, this is just a specific scenario (controlled inputs) where you need onChange events specified for each element.
  4. Not entirely sure what the ask on this one is, but I used refs instead of target.name.
  5. So, you're correct in that you should never alter the state directly, and this is a tricky bit from the docs. React is going to alter state directly, it's just going to do it in the implementation through setState. If you alter state outside of this method call, unexpected things will happen and errors will be thrown.

shouldComponentUpdate only does shallow comparisons, but there are a few solutions here.

One is to stringify the objects, this is a quick and dirty object comparison, don't really recommend it, but it works.

A better solution, and one I have used with React + Flux is to implement a propertyChanged bool, and just check that in your shouldComponentUpdate.

Now, this will require you to be aware of setting it when things change, i.e., you changed something deeper in the object graph. Say propertyOne is an object with a property that gets changed in your handleChange method. You would validate the input however you wish, then set propertyChanged = true, and you then need to implement componentDidUpdate. We're making an assumption here, but if the component has updated, you set propertyChanged back to false so you don't have any further triggering of unwanted updates. I hope that makes sense. It's kinda like a one-way notifyPropertyChanged.

I'm providing a quick example of what I would probably do for a more dynamic implementation that allows you to add more properties to your object (only shallow properties in this implementation, obviously you could write a more robust solution). Let me know if you have any further questions or if I didn't answer something.

http://jsfiddle.net/rpv9trhh/

var e = {     prop1: 'test',     prop2: 'wee',     prop3: 'another property',     propFour: 'Oh yeah!' };  var FormComp = React.createClass({     getInitialState: function(){         return {             entity: this.props.entity         }     },     render: function() {          var ent = this.state.entity;         var that = this;         var inputs = [];          for(var key in ent){             inputs.push(<input                 key={key}                 style={{display:'block'}}                 type="text"                 ref={key} onChange={that._propertyChanged.bind(null, key)}                value={ent[key]} />)         }          return <form>             {inputs}             <input                  type="button"                  onClick={this._saveChanges}                  value="Save Changes" />         </form>;     },     _propertyChanged: function(propName) {         var nextProp = this.refs[propName].getDOMNode().value;         var nextEntity = this.state.entity;         nextEntity[propName] = nextProp;          this.setState({             entity: nextEntity         });     },     _saveChanges: function() {         var updatedEntity = this.state.entity;          for(var key in updatedEntity){             alert(updatedEntity[key]);         }          //TODO: Call to service to save the entity, i.e.             ActionCreators.saveEntity(updatedEntity);     } });  React.renderComponent(<FormComp entity={e} />, document.body); 
like image 71
captray Avatar answered Sep 22 '22 13:09

captray