Forms

In this final section of the chapter, you'll implement some form components from react-bootstrap. Just like the filter buttons you created in the preceding section, form components have state that needs to be passed down from a container component.

However, even simple form controls have many moving parts. First, you'll learn about text inputs. There's the input itself, but there's also the label, the placeholder, the error text, the validation function, and so on. To help glue all these pieces together, let's create a generic component that encapsulates all of the Bootstrap parts:

import React from 'react';
import PropTypes from 'prop-types';
import {
FormGroup,
FormControl,
ControlLabel,
HelpBlock
} from 'react-bootstrap';

// A generic input element that encapsulates several
// of the react-bootstrap components that are necessary
// for event simple scenarios.
const Input = ({
type,
label,
value,
placeholder,
onChange,
validationState,
validationText
}) => (
<FormGroup validationState={validationState}>
<ControlLabel>{label}</ControlLabel>
<FormControl
type={type}
value={value}
placeholder={placeholder}
onChange={onChange}
/>
<FormControl.Feedback />
<HelpBlock>{validationText}</HelpBlock>
</FormGroup>
);

Input.propTypes = {
type: PropTypes.string.isRequired,
label: PropTypes.string,
value: PropTypes.any,
placeholder: PropTypes.string,
onChange: PropTypes.func,
validationState: PropTypes.oneOf([
undefined,
'success',
'warning',
'error'
]),
validationText: PropTypes.string
};

export default Input;

There are two key advantages to this approach. One is that, instead of having to use <FormGroup>, <FormControl>, <HelpBlock>, and so on, you just need your <Input> element. Another advantage is that only the type property is required, meaning that <Input> can be used for simple and complex controls.

Let's see this component in action now:

import React from 'react';
import PropTypes from 'prop-types';
import { Panel } from 'react-bootstrap';

import Input from './Input';

const InputsForm = props => (
<Panel header={<h3>Inputs</h3>}>
<form>
{/* Uses the <Input> element to render
a simple name field. There's a lot of
properties passed here, many of them
come from the container component. */}
<Input
type="text"
label="Name"
placeholder="First and last..."
value={props.nameValue}
onChange={props.nameChange}
validationState={props.nameValidationState}
validationText={props.nameValidationText}
/>

{/* Uses the "<Input>" element to render a
password input. */}
<Input
type="password"
label="Password"
value={props.passwordValue}
onChange={props.passwordChange}
/>
</form>
</Panel>
);

InputsForm.propTypes = {
nameValue: PropTypes.any,
nameChange: PropTypes.func,
nameValidationState: PropTypes.oneOf([
undefined,
'success',
'warning',
'error'
]),
nameValidationText: PropTypes.string,
passwordValue: PropTypes.any,
passwordChange: PropTypes.func
};

export default InputsForm;

There's only one component used to create all of the necessary Bootstrap pieces underneath. Everything is passed in through a property. Here's what this form looks like:

Now let's look at the container component that controls the state of these inputs:

import React, { Component } from 'react';
import { fromJS } from 'immutable';

import InputsForm from './InputsForm';

// Validates the given "name". It should have a space,
// and it should have more than 3 characters. There are
// many scenarios not accounted for here, but are easy
// to add.
function validateName(name) {
if (name.search(/ /) === -1) {
return 'First and last name, separated with a space';
} else if (name.length < 4) {
return 'Less than 4 characters? Srsly?';
}

return null;
}

class InputsFormContainer extends Component {
state = {
data: fromJS({
// "Name" value and change handler.
nameValue: '',
// When the name changes, we use "validateName()"
// to set "nameValidationState" and
// "nameValidationText".
nameChange: e => {
this.data = this.data.merge({
nameValue: e.target.value,
nameValidationState:
validateName(e.target.value) === null
? 'success'
: 'error',
nameValidationText: validateName(e.target.value)
});
},
// "Password" value and change handler.
passwordValue: '',
passwordChange: e => {
this.data = this.data.set('passwordValue', e.target.value);
}
})
};

// Getter for "Immutable.js" state data...
get data() {
return this.state.data;
}

// Setter for "Immutable.js" state data...
set data(data) {
this.setState({ data });
}

render() {
return <InputsForm {...this.data.toJS()} />;
}
}

export default InputsFormContainer;

The event handlers for the inputs are part of the state that get passed to InputsForm as properties. Now let's take a look at some checkboxes and radio buttons. You'll use the <Radio> and the <Checkbox> react-bootstrap components:

import React from 'react';
import PropTypes from 'prop-types';
import { Panel, Radio, Checkbox, FormGroup } from 'react-bootstrap';

const RadioForm = props => (
<Panel header={<h3>Radios & Checkboxes</h3>}>
{/* Renders a group of related radio buttons. Note
that each radio needs to hae the same "name"
property, otherwise, the user will be able to
select multiple radios in the same group. The
"checked", "disabled", and "onChange" properties
all come from the container component. */}
<FormGroup>
<Radio
name="radio"
onChange={props.checkboxEnabledChange}
checked={props.checkboxEnabled}
disabled={!props.radiosEnabled}
>
Checkbox enabled
</Radio>
<Radio
name="radio"
onChange={props.checkboxDisabledChange}
checked={!props.checkboxEnabled}
disabled={!props.radiosEnabled}
>
Checkbox disabled
</Radio>
</FormGroup>

{/* Reanders a checkbox and uses the same approach
as the radios above: setting it's properties from
state that's passed in from the container. */}
<FormGroup>
<Checkbox
onChange={props.checkboxChange}
checked={props.radiosEnabled}
disabled={!props.checkboxEnabled}
>
Radios enabled
</Checkbox>
</FormGroup>
</Panel>
);

RadioForm.propTypes = {
checkboxEnabled: PropTypes.bool.isRequired,
radiosEnabled: PropTypes.bool.isRequired,
checkboxEnabledChange: PropTypes.func.isRequired,
checkboxDisabledChange: PropTypes.func.isRequired,
checkboxChange: PropTypes.func.isRequired
};

export default RadioForm;

The radio buttons toggle the enabled state of the checkbox and the checkbox toggles the enabled state of the radios. Note that, although the two <Radio> elements are in the same <FormGroup>, they need to have the same name property value. Otherwise, you'll be able to select both radios at the same time. Here's what this form looks like:

Finally, let's look at the container component that handles the state of the radios and the checkbox:

import React, { Component } from 'react';
import { fromJS } from 'immutable';

import RadioForm from './RadioForm';

class RadioFormContainer extends Component {
// Controls the enabled state of a group of
// radio buttons and a checkbox. The radios
// toggle the state of the checkbox while the
// checkbox toggles the state of the radios.
state = {
data: fromJS({
checkboxEnabled: false,
radiosEnabled: true,
checkboxEnabledChange: () => {
this.data = this.data.set('checkboxEnabled', true);
},
checkboxDisabledChange: () => {
this.data = this.data.set('checkboxEnabled', false);
},
checkboxChange: () => {
this.data = this.data.update(
'radiosEnabled',
enabled => !enabled
);
}
})
};

// Getter for "Immutable.js" state data...
get data() {
return this.state.data;
}

// Setter for "Immutable.js" state data...
set data(data) {
this.setState({ data });
}

render() {
return <RadioForm {...this.data.toJS()} />;
}
}

export default RadioFormContainer;
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.22.242.141