Skip to content

Instantly share code, notes, and snippets.

@andrew--grant
Created December 9, 2016 05:06
Show Gist options
  • Save andrew--grant/8e621b6fb305e7bcf0a8405afa7ff170 to your computer and use it in GitHub Desktop.
Save andrew--grant/8e621b6fb305e7bcf0a8405afa7ff170 to your computer and use it in GitHub Desktop.
React Form
<div id="app"></app>
class ContactForm extends React.Component {
constructor(props) {
super(props)
// this is all internal/local state as
// the form gathers data and becomes a
// 'submittable' data-set
this.state = {
form: {
valid: false,
salutation: '',
firstName: '',
surname: '',
emailAddress: '',
comments: '',
colour: ''
}
}
this.validation = {
// keys must match the id of
// the associated field components
salutation: [
{
rule: () => {
return this.state.form.salutation.length > 0
},
message: 'Salutation is required'
},
{
rule: () => {
return this.state.form.salutation.indexOf('M') > -1
},
message: 'Salutation must contain an "M"'
}
],
colour: [
{
rule: () => {
return this.state.form.colour.length != ''
},
message: 'Colour is required'
}
]
}
}
isFormValid() {
// see if all form level validation is satisfied
let formValid = true
for (const key of Object.keys(this.validation)) {
for (let validation of this.validation[key]) {
if (!validation.rule()) {
formValid = false
continue
}
}
if (!formValid) {
return formValid
}
}
return formValid
}
handleInputChange(evt) {
// Track form state
let form = this.state.form
form[evt.target.name] = evt.target.value
// run validation on every keystroke
// todo: this could be an option: onlyValidateFormOnSave="true"
if (!this.isFormValid()) {
form.valid = false
} else {
form.valid = true
}
this.setState({form})
}
handleSubmit(evt) {
evt.preventDefault()
}
render() {
return (
<form onSubmit={(evt) => this.handleSubmit(evt)}>
<TextInputSingle onChange={(evt) => {
this.handleInputChange(evt)
}}
id="salutation"
label="Salutation"
validation={this.validation.salutation}/>
<TextInputSingle onChange={(evt) => {
this.handleInputChange(evt)
}} id="firstName" value={this.state.form.firstName}/>
<TextInputSingle onChange={(evt) => {
this.handleInputChange(evt)
}} id="surname" value={this.state.form.surname}/>
<TextInputMulti label="Your Comments" id="comments">{this.state.form.comments}</TextInputMulti>
<SelectSingle id="colour"
label="Choose a colour"
value={this.state.form.colour}
options={[{value: '', label: 'Choose colour'},
{value: 'red', label: 'Red'},
{value: 'blue', label: 'Blue'}]}
validation={this.validation.colour}
onChange={(evt) => {
this.handleInputChange(evt)
}}
/>
<p>This form is {this.state.form.valid ? 'valid' : 'not valid'}</p>
<button type="submit">Submit</button>
</form>
)
}
}
class SelectSingle extends React.Component {
constructor(props) {
super(props)
this.state = {value: '', valid: true, pristine: true, errorMessages: []}
}
handleChange(evt) {
if (this.props.onChange) {
this.props.onChange(evt)
}
// controlled component, update value
this.setState({value: evt.target.value})
// the field is no longer pristine
this.setState({pristine: false})
// If any validations apply them here
let valid = true;
if (this.props.validation) {
this.state.errorMessages = [];
for (let validation of this.props.validation) {
if (!validation.rule()) {
this.setState({valid: false})
this.state.errorMessages.push(validation.message)
} else {
this.setState({valid: true})
}
}
}
}
render() {
return (
<div className="select">
<label htmlFor={this.props.id}>{this.props.label}</label>
<select className={(this.state.pristine ? 'input-pristine' : 'input-dirty') + ' ' + (this.state.valid ? 'input-valid' : 'input-invalid')}
name={this.props.id} id={this.props.id}
onChange={(evt) => {
this.handleChange(evt)
}}>
{
this.props.options.map((opt, index) => {
return <option key={index} value={opt.value}>{opt.label}</option>
})
}
</select>
<div>
{
this.state.errorMessages.map(
function (val, index) {
return <p className="error-message" key={index}>{val}</p>
}
)}
</div>
</div>
);
}
}
class TextInputMulti extends React.Component {
constructor(props) {
super(props)
this.state = {value: '', valid: true, pristine: true, errorMessages: []}
}
handleChange(evt) {
if (this.props.onChange) {
this.props.onChange(evt)
}
// controlled component, update value
this.setState({value: evt.target.value})
// the field is no longer pristine
this.setState({pristine: false})
// If any validations apply them here
let valid = true;
if (this.props.validation) {
this.state.errorMessages = [];
for (let validation of this.props.validation) {
if (!validation.rule()) {
this.setState({valid: false})
this.state.errorMessages.push(validation.message)
} else {
this.setState({valid: true})
}
}
}
}
render() {
return (
<div className="text-input-single">
<label htmlFor={this.props.id}>{this.props.label}</label>
<textarea
className={(this.state.pristine ? 'input-pristine' : 'input-dirty') + ' ' + (this.state.valid ? 'input-valid' : 'input-invalid')}
id={this.props.id}
name={this.props.id}
value={this.state.value}
onChange={(evt) => {
this.handleChange(evt)
}}/>
<div>
{
this.state.errorMessages.map(
function (val, index) {
return <p className="error-message" key={index}>{val}</p>
}
)}
</div>
</div>
);
}
}
class TextInputSingle extends React.Component {
constructor(props) {
super(props)
this.state = {value: '', valid: true, pristine: true, errorMessages: []}
}
handleChange(evt) {
if (this.props.onChange) {
this.props.onChange(evt)
}
// controlled component, update value
this.setState({value: evt.target.value})
// the field is no longer pristine
this.setState({pristine: false})
// If any validations apply them here
let valid = true;
if (this.props.validation) {
this.state.errorMessages = [];
for (let validation of this.props.validation) {
if (!validation.rule()) {
this.setState({valid: false})
this.state.errorMessages.push(validation.message)
} else {
this.setState({valid: true})
}
}
}
}
render() {
return (
<div className="text-input-single">
<label htmlFor={this.props.id}>{this.props.label}</label>
<input
className={(this.state.pristine ? 'input-pristine' : 'input-dirty') + ' ' + (this.state.valid ? 'input-valid' : 'input-invalid')}
id={this.props.id}
name={this.props.id}
type='text'
value={this.state.value}
onChange={(evt) => {
this.handleChange(evt)
}}/>
<div>
{
this.state.errorMessages.map(
function (val, index) {
return <p className="error-message" key={index}>{val}</p>
}
)}
</div>
</div>
);
}
}
/*
* A simple React component
*/
class Application extends React.Component {
render() {
return <div>
<h1>React Based Form</h1>
<ContactForm/>
</div>;
}
}
/*
* Render the above component into the div#app
*/
ReactDOM.render(<Application />, document.getElementById('app'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.3.1/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.3.1/react-dom.min.js"></script>
.input-pristine {
}
.input-dirty {
}
.input-invalid {
border: solid 2px red;
}
.input-valid.input-dirty {
border: solid 2px green;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment