Reputation: 2550
I have the following component hierarchy:
<Form>
<div>
<Label>
<Input name="first_name" />
</Label>
</div>
<Label>
<Input name="first_name" />
</Label>
</Form
I would like to implement the following behaviors:
All <Form>
components should implement a behavior that will autofocus
the first <Input>
, without the <Input>
having to specify this each time again and again. Using the autofocus
manually in every <Form>
is prone to error, and developers tend to forget it.
As for now, I've decided using a code like this inside <Form>
component:
componentDidMount() { $(ReactDOM.findDOMNode(this)).find('input:first:visible').focus() }
<Label>
elements should have a for
/htmlFor
+ <Input>
attribute that matches the <Input>
id
inside the <Label>
without the developer having to specify it manually each time. I am considering using a recursive cloneElement
and injecting the for
and id
attributes but this sounds too cumbersome and not elegant.
Any ideas?
Upvotes: 1
Views: 355
Reputation: 3114
So I think we can accomplish what you're looking for by creating a few custom components: Form
and FormGroup
.
Form
will be responsible for setting a prop on the FormGroup
specifying whether or not it should be focused (I'm assuming that all of Form
's children are FormGroup
instances):
class Form extends React.Component {
render() {
const children = React.Children.map(this.props.children, (el, i) => {
const focused = i === 0;
return React.cloneElement(el, { focused });
});
return <form>{children}</form>;
}
}
And our FormGroup
component has a few responsibilities.
htmlFor
attribute for the label elementThis code is a bit more involved:
class FormGroup extends React.Component {
render() {
let input = null;
let label = null;
// Get references to the input and label elements
React.Children.forEach(this.props.children, el => {
switch (el.type) {
case 'input':
input = el;
return;
case 'label':
label = el;
return;
}
});
if (input === null || label === null) {
throw new Error('FormGroup must be used with and input and label element');
}
// Augment: add the htmlFor and autoFocus attributes
label = React.cloneElement(label, { htmlFor: input.props.id });
input = React.cloneElement(input, { autoFocus: this.props.focused });
// Render our augmented instances
return <div>{label}{input}</div>;
}
}
Now that we have our building blocks, we can create forms with the desired behavior:
<Form>
<FormGroup>
<label>First Label</label>
<input id="first" type="text" />
</FormGroup>
<FormGroup>
<label>Second Label</label>
<input id="second" type="text" />
</FormGroup>
</Form>
For this form, the #first
input would be focused and each label element would have the correct for
attributes.
Hopefully this will get you on the right track. Here's a webpackbin of this setup: http://www.webpackbin.com/VJVY1a7Tg
Upvotes: 1