Somazx
Somazx

Reputation: 532

How do I reduce the amount of markup in my reactjs/JSX

I want to cleanup my JSX to separate the presentation markup from the functional markup/DOM elements.

I have a form which will be the top-level of my component hierarchy. Underneath there will be several inputs and form elements to make up the complete component hierarchy. There will also be a fair amount of twitter bootstrap "cruft" that is mainly there to control presentation.

For example:

render: function() {
  return (
    <form role="form" className="form-horizontal">
      <div className="form-group">
        <label htmlFor="homePrice">Home Price</label>
        <div className="input-group input-group-lg">
          <span className="input-group-addon">$</span>
          <input id="homePrice"
            className="form-control"
            onChange={this.priceChange}
            value={this.state.homePrice}
          />
        </div>
      </div>
      <div className="form-group">
        <label htmlFor="depositAmount">Deposit Amount</label>
        <div className="input-group input-group-lg">
          <span className="input-group-addon">$</span>
          <input id="depositAmount"
            className="form-control"
            onChange={this.depositChange}
            value={this.state.depositAmount}
          />
        </div>
      </div>
      ... snip ...

Ideally I'd have separate components for <form> and then individual child components for each of the <inputs>, without the label tags or <div className="form-group"> and <span className="input-group-addon"> and be able to insert those into various points in the HTML document and maintain component hierarchy.

So how can I accomplish this and maintain the component hierarchy. Changing values in the inputs affects state in the parent, top-most component.

Thanks.

Upvotes: 2

Views: 717

Answers (1)

Michelle Tilley
Michelle Tilley

Reputation: 159105

You can do this quite effectively via composition. Just how customizable you want your custom components to be is up to you; with more props and conditional logic, you gain more customizability.

I'll often start by writing the markup/JSX I wish I could use:

render: function() {
  return (
    <Form horizontal>
      <Input id="homePrice"
             addon="$" label="Home Price"
             onChange={this.priceChange}
             value={this.state.homePrice} />
      <Input id="depositAmount"
             addon="$" label="Deposit Amount"
             onChange={this.depositChange}
             value={this.state.depositAmount} />
    </Form>
  );
}

Then you can start implementing the components as necessary to make it work:

var Form = React.createClass({
  render: function() {
    var direction = "vertical";
    if (this.props.horizontal) direction="horizontal";

    return (
      <form role="form" className={"form-" + direction}
            onSubmit={this.props.onSubmit}>
        {this.props.children}
      </form>
    );
  }
});

var Input = React.createClass({
  render: function() {
    return (
      <div className="form-group">
        <label htmlFor={this.props.id}>{this.props.label}</label>
        <div className="input-group input-group-lg">
          <span className="input-group-addon">{this.props.addon}</span>
          <input id={this.props.id}
                 className="form-control"
                 onChange={this.props.onChange}
                 value={this.props.value} />
        </div>
      </div>
    );
  }
});

By utilizing this.props.children, getDefaultProps, the spread operator (e.g. transferring props to a new element via <someElement {...this.props} />), and conditionals, you can create really nice abstractions for more complex components. Additionally, propTypes allows to you specify what properties a component takes, serving as documentation and a way to catch runtime errors in development.

You might consider checking out the source for React Bootstrap to see how they implement composite components to wrap more complex Bootstrap HTML; for example, here's what you'd write to use a tab component:

<TabbedArea defaultActiveKey={2}>
  <TabPane eventKey={1} tab="Tab 1">TabPane 1 content</TabPane>
  <TabPane eventKey={2} tab="Tab 2">TabPane 2 content</TabPane>
</TabbedArea>

If you're more interested in describing the structure of your forms and then letting a library create the elements automatically, you might look into projects like React Forms:

function Person(props) {
  props = props || {}
  return (
    <Schema name={props.name} label={props.label}>
      <Property name="first" label="First name" />
      <Property name="last" label="Last name" />
    </Schema>
  )
}

var family = (
  <Schema>
    <Person name="mother" label="Mother" />
    <Person name="father" label="Father" />
    <List name="children" label="Children">
      <Person />
    </List>
  </Schema>
)

React.renderComponent(
  <Form schema={family} />,
  document.getElementById('example'))

Upvotes: 6

Related Questions