Reputation: 532
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
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