Make react components less verbose

- 1 answer

Ad

I'll show the entire code at the end of my question. But the thing I want to focus on is this render function in my App.jsx react component

render() {
    return (
      <div className="container">
          <ul>
            {this.renderListApplications()}
          </ul>
          <div>{JSON.stringify(this.data.currentApplication)}</div>
          <form className="new-task" onSubmit={this.handleSubmit} >
            <input ref="input_36" id="input_36" type="text" value={this.state.input_36} onChange={this.handleChange} />
            <input ref="input_37" id="input_37" type="text" value={this.state.input_37} onChange={this.handleChange} />
            <textarea ref="input_38" id="input_38" onChange={this.handleChange} value={this.state.input_38} />
            <textarea ref="input_39" id="input_39" onChange={this.handleChange} value={this.state.input_39} />
            <input ref="input_40" id="input_40" type="text" value={this.state.input_40} onChange={this.handleChange} />
            <button type="submit">Submit</button>
          </form>
      </div>
    );
}

I find ref="input_36" id="input_36" type="text" value={this.state.input_36} onChange={this.handleChange} to be too verbose. If I had about 300 other input, textarea, select fields interspersed between a whole bunch of other html elements, I'd be looking to reduce the amount of typing wherever possible. Is there a more convenient way to programmatically set some of these attributes and event handlers of the input, textarea and select fields? Or a react setting somewhere to default some of these things? Or just some way to make things less verbose?

If this were PHP, I'd put all my html code into a form.html, then I'd programmatically set all those attributes with something like:

/* I could simplify this code even more, but I'm making it verbose so you understand what I'm trying to achieve. */
$htmlForm = new DOMDocument();
$htmlForm->loadHTMLFile('form.html');
foreach ($htmlForm->getElementsByTagName('input') as $i)
{
    $i->setAttribute('value',$arrState[$i->getAttribute('name')]);
    $i->setAttribute('onChange','javascript:somefunction()');
    etc...
}
foreach ($htmlForm->getElementsByTagName('textarea') as $i)
{
    $i->nodeValue = $arrState[$i->getAttribute('name')];
    $i->setAttribute('onChange','javascript:somefunction()');
    etc...
}   

When I tried various approaches to programmatically set these attributes in React and JS, I kept running into errors about whatever DOM or element or whatever doesn't exist, or my app would crash,or whatever. If someone can show me an example of how to make this less verbose, that would be great.

Now here's my full App.jsx file.

App = React.createClass({
  mixins: [ReactMeteorData],

  getMeteorData() {
    return {
      applications: Applications.find({}, {sort: {createdAt: -1}}).fetch(),
      currentApplication: Applications.findOne({_id:this.props.router.params.appid}, {sort: {createdAt: -1}}),
    }
  },
  getInitialState: function() {
    return this.loadForm(this.props.router.params.appid);
  },
  loadForm(appId) {
    var currentApp = Applications.findOne({_id:appId});
    if(!currentApp) currentApp = {};
    return currentApp;
  },
  clickLoadForm(appId)
  {
    var currentApp = this.loadForm(appId);
    var state = new Object();
    var refs = this.refs;
    Object.keys(refs).map(function(prop,index){
      state[prop] = typeof currentApp[prop] == 'undefined' ? "" : currentApp[prop];
    });
    this.setState(state);
  },
  renderListApplications() {
    var _this = this;
    return this.data.applications.map(function(applicationform,i) {
      return <li key={"li"+i}><a onClick={_this.clickLoadForm.bind(_this,applicationform._id)} href={Meteor.absoluteUrl()+'application/' +applicationform._id} key={"a"+i}>Version {applicationform._id}</a></li>;
    });
  },
  handleSubmit(event) {
    event.preventDefault();
    var refs = this.refs;
    var formVals = new Object();
    Object.keys(refs).map(function(prop, index){
      if(refs[prop].nodeName.match(/(INPUT|SELECT|TEXTAREA)/).length > 0)
        formVals[prop] = refs[prop].value;
    });

    Meteor.call("saveApplication", formVals);

  },
  handleChange: function(e) {
    if(!e.target.id) return;
    if(typeof e.target.id == 'undefined') return;
    var state = new Object();
    state[e.target.id] = e.target.value;

    this.setState(state);
  },
  render() {
    return (
      <div className="container">
          <ul>
            {this.renderListApplications()}
          </ul>
          <div>{JSON.stringify(this.data.currentApplication)}</div>
          <form className="new-task" onSubmit={this.handleSubmit} >
            <input ref="input_36" id="input_36" type="text" value={this.state.input_36} onChange={this.handleChange} />
            <input ref="input_37" id="input_37" type="text" value={this.state.input_37} onChange={this.handleChange} />
            <textarea ref="input_38" id="input_38" onChange={this.handleChange} value={this.state.input_38} />
            <textarea ref="input_39" id="input_39" onChange={this.handleChange} value={this.state.input_39} />
            <input ref="input_40" id="input_40" type="text" value={this.state.input_40} onChange={this.handleChange} />
            <button type="submit">Submit</button>
          </form>
      </div>
    );
  }
});

Notes

  • I want to be able to edit the form fields even when there is a value in it
  • When someone clicks on a link from renderListApplications, I want the form to re-render with the relevant values corresponding to the :appid in the url
Ad

Answer

Ad

You could simply create functions in your component that build parts of your UI (like a factory). Here is an example:

getInputField(id){
    const inputId = `input_${id}`;

    return(
        <input ref={inputId}
               id={inputId}
               type="text"
               value={this.state[`input${id}`]}
               onChange={this.handleChange} />
    );
}

render(){
    const input36 = this.getInputField(36);
    const input37 = this.getInputField(37);

    return (
        <div className="container">
          <ul>
            {this.renderListApplications()}
          </ul>
          <div>{JSON.stringify(this.data.currentApplication)}</div>
          <form className="new-task" onSubmit={this.handleSubmit} >
            {input36}
            {input37}
            ...
          </form>
        </div>
    );
}
Ad
source: stackoverflow.com
Ad