I have a simple form. All of the components and state are held in the Page component. There are 2 display headers and 3 input fields. The first input is supposed to be text, and the second and third are supposed to be ints. When the user inputs the wrong type of data, I want to have an error message pop up next to the input field. My questions relate to best practices in React.JS
Who decides that the value is in valid? I suppose that the only job of the input field is to direct the value back to component holding the state, so does this mean that only Page can determine if a value is valid?
How should I then have the pop up appear? Should Page have to trigger a new boolean state element that will be passed through perp that will tell Adaptive_Input to reveal the error message?
JSFiddle
JS:
/**
* @jsx React.DOM
*/
var Adaptive_Input = React.createClass({
handle_change: function(){
var new_text = this.refs.input.getDOMNode().value;
this.props.on_Input_Change(new_text);
},
render: function(){
return (
<div className='adaptive_placeholder_input_container'>
<input
className="adaptive_input"
type="text"
required="required"
onChange= {this.handle_change}
ref="input"
></input>
<label
className="adaptive_placeholder"
alt={this.props.initial}
placeholder={this.props.focused}
></label>
</div>
);
}
});
var Form = React.createClass({
render: function(){
return (
<form>
<Adaptive_Input
initial={'Name Input'}
focused={'Name Input'}
on_Input_Change={this.props.handle_text_input}
/>
<Adaptive_Input
initial={'Value 1'}
focused={'Value 1'}
on_Input_Change={this.props.handle_value_1_input}
/>
<Adaptive_Input
initial={'Value 2'}
focused={'Value 2'}
on_Input_Change={this.props.handle_value_2_input}
/>
</form>
);
}
});
var Page = React.createClass({
getInitialState: function(){
return {
Name : "No Name",
Value_1 : '0',
Value_2 : '0',
Display_Value: '0'
};
},
handle_text_input: function(new_text){
this.setState({
Name: new_text
});
},
handle_value_1_input: function(new_value){
console.log("===");
var updated_display = parseInt(new_value) + parseInt(this.state.Value_2);
updated_display = updated_display.toString();
this.setState({
Display_Value: updated_display
});
},
handle_value_2_input: function(new_value){
var updated_display = parseInt(this.state.Value_1) + parseInt(new_value);
updated_display = updated_display.toString();
this.setState({
Display_Value: updated_display
});
},
render: function(){
return(
<div>
<h2>{this.state.Name}</h2>
<h2>Value 1 + Value 2 = {this.state.Display_Value}</h2>
<Form
handle_text_input={this.handle_text_input}
handle_value_1_input = {this.handle_value_1_input}
handle_value_2_input = {this.handle_value_2_input}
/>
</div>
);
}
});
React.renderComponent(<Page />, document.body);
Form validation in React allows an error message to be displayed if the user has not correctly filled out the form with the expected type of input. There are several ways to validate forms in React; however, this shot will focus on creating a validator function with validation rules.
Validation should aim to be as accommodating as possible of different forms of input for particular data types. For example, telephone numbers are written with different separators and digit groupings. Your form will be easier to use if it can interpret multiple notations. Also, it is helpful to be liberal with input.
To add form validation with React and Material UI, we can set the error prop of the TextField to show the error state when there's an error. We can set the helperText prop to show the error message. We add a TextField with the value prop set to text to show the inputted text.
First, here is an example of what I'll mention below: http://jsbin.com/rixido/2/edit
How to properly validate input values with React.JS?
However you want. React is for rendering a data model. The data model should know what is valid or not. You can use Backbone models, JSON data, or anything you want to represent the data and it's error state.
More specifically:
React is generally agnostic towards your data. It's for rendering and dealing with events.
The rules to follow are:
How to decide if something should be a prop or a state? Consider this: would ANY part of your app other than the text field want to know that the value entered is bad? If no, make it a state. If yes, it should be a prop.
For example, if you wanted a separate view to render "You have 2 errors on this page." then your error would have to be known to a toplevel data model.
Where should that error live?
If your app was rendering Backbone models (for example), the model itself would have a validate() method and validateError property you could use. You could render other smart objects that could do the same. React also says try to keep props to a minimum and generate the rest of the data. so if you had a validator (e.g. https://github.com/flatiron/revalidator) then your validations could trickle down and any component could check props with it's matching validation to see if it's valid.
It's largely up to you.
(I am personally using Backbone models and rendering them in React. I have a toplevel error alert that I show if there is an error anywhere, describing the error.)
You can use npm install --save redux-form
Im writing a simple email and submit button form, which validates email and submits form. with redux-form, form by default runs event.preventDefault() on html onSubmit action.
import React, {Component} from 'react';
import {reduxForm} from 'redux-form';
class LoginForm extends Component {
onSubmit(props) {
//do your submit stuff
}
render() {
const {fields: {email}, handleSubmit} = this.props;
return (
<form onSubmit={handleSubmit(this.onSubmit.bind(this))}>
<input type="text" placeholder="Email"
className={`form-control ${email.touched && email.invalid ? 'has-error' : '' }`}
{...email}
/>
<span className="text-help">
{email.touched ? email.error : ''}
</span>
<input type="submit"/>
</form>
);
}
}
function validation(values) {
const errors = {};
const emailPattern = /(.+)@(.+){2,}\.(.+){2,}/;
if (!emailPattern.test(values.email)) {
errors.email = 'Enter a valid email';
}
return errors;
}
LoginForm = reduxForm({
form: 'LoginForm',
fields: ['email'],
validate: validation
}, null, null)(LoginForm);
export default LoginForm;
Your jsfiddle does not work anymore. I've fixed it: http://jsfiddle.net/tkrotoff/bgC6E/40/ using React 16 and ES6 classes.
class Adaptive_Input extends React.Component {
handle_change(e) {
var new_text = e.currentTarget.value;
this.props.on_Input_Change(new_text);
}
render() {
return (
<div className="adaptive_placeholder_input_container">
<input
className="adaptive_input"
type="text"
required="required"
onChange={this.handle_change.bind(this)} />
<label
className="adaptive_placeholder"
alt={this.props.initial}
placeholder={this.props.focused} />
</div>
);
}
}
class Form extends React.Component {
render() {
return (
<form>
<Adaptive_Input
initial={'Name Input'}
focused={'Name Input'}
on_Input_Change={this.props.handle_text_input} />
<Adaptive_Input
initial={'Value 1'}
focused={'Value 1'}
on_Input_Change={this.props.handle_value_1_input} />
<Adaptive_Input
initial={'Value 2'}
focused={'Value 2'}
on_Input_Change={this.props.handle_value_2_input} />
</form>
);
}
}
class Page extends React.Component {
constructor(props) {
super(props);
this.state = {
Name: 'No Name',
Value_1: '0',
Value_2: '0',
Display_Value: '0'
};
}
handle_text_input(new_text) {
this.setState({
Name: new_text
});
}
handle_value_1_input(new_value) {
new_value = parseInt(new_value);
var updated_display = new_value + parseInt(this.state.Value_2);
updated_display = updated_display.toString();
this.setState({
Value_1: new_value,
Display_Value: updated_display
});
}
handle_value_2_input(new_value) {
new_value = parseInt(new_value);
var updated_display = parseInt(this.state.Value_1) + new_value;
updated_display = updated_display.toString();
this.setState({
Value_2: new_value,
Display_Value: updated_display
});
}
render() {
return(
<div>
<h2>{this.state.Name}</h2>
<h2>Value 1 + Value 2 = {this.state.Display_Value}</h2>
<Form
handle_text_input={this.handle_text_input.bind(this)}
handle_value_1_input={this.handle_value_1_input.bind(this)}
handle_value_2_input={this.handle_value_2_input.bind(this)}
/>
</div>
);
}
}
ReactDOM.render(<Page />, document.getElementById('app'));
And now the same code hacked with form validation thanks to this library: https://github.com/tkrotoff/react-form-with-constraints => http://jsfiddle.net/tkrotoff/k4qa4heg/
const { FormWithConstraints, FieldFeedbacks, FieldFeedback } = ReactFormWithConstraints;
class Adaptive_Input extends React.Component {
static contextTypes = {
form: PropTypes.object.isRequired
};
constructor(props) {
super(props);
this.state = {
field: undefined
};
this.fieldWillValidate = this.fieldWillValidate.bind(this);
this.fieldDidValidate = this.fieldDidValidate.bind(this);
}
componentWillMount() {
this.context.form.addFieldWillValidateEventListener(this.fieldWillValidate);
this.context.form.addFieldDidValidateEventListener(this.fieldDidValidate);
}
componentWillUnmount() {
this.context.form.removeFieldWillValidateEventListener(this.fieldWillValidate);
this.context.form.removeFieldDidValidateEventListener(this.fieldDidValidate);
}
fieldWillValidate(fieldName) {
if (fieldName === this.props.name) this.setState({field: undefined});
}
fieldDidValidate(field) {
if (field.name === this.props.name) this.setState({field});
}
handle_change(e) {
var new_text = e.currentTarget.value;
this.props.on_Input_Change(e, new_text);
}
render() {
const { field } = this.state;
let className = 'adaptive_placeholder_input_container';
if (field !== undefined) {
if (field.hasErrors()) className += ' error';
if (field.hasWarnings()) className += ' warning';
}
return (
<div className={className}>
<input
type={this.props.type}
name={this.props.name}
className="adaptive_input"
required
onChange={this.handle_change.bind(this)} />
<label
className="adaptive_placeholder"
alt={this.props.initial}
placeholder={this.props.focused} />
</div>
);
}
}
class Form extends React.Component {
constructor(props) {
super(props);
this.state = {
Name: 'No Name',
Value_1: '0',
Value_2: '0',
Display_Value: '0'
};
}
handle_text_input(e, new_text) {
this.form.validateFields(e.currentTarget);
this.setState({
Name: new_text
});
}
handle_value_1_input(e, new_value) {
this.form.validateFields(e.currentTarget);
if (this.form.isValid()) {
new_value = parseInt(new_value);
var updated_display = new_value + parseInt(this.state.Value_2);
updated_display = updated_display.toString();
this.setState({
Value_1: new_value,
Display_Value: updated_display
});
}
else {
this.setState({
Display_Value: 'Error'
});
}
}
handle_value_2_input(e, new_value) {
this.form.validateFields(e.currentTarget);
if (this.form.isValid()) {
new_value = parseInt(new_value);
var updated_display = parseInt(this.state.Value_1) + new_value;
updated_display = updated_display.toString();
this.setState({
Value_2: new_value,
Display_Value: updated_display
});
}
else {
this.setState({
Display_Value: 'Error'
});
}
}
render() {
return(
<div>
<h2>Name: {this.state.Name}</h2>
<h2>Value 1 + Value 2 = {this.state.Display_Value}</h2>
<FormWithConstraints ref={form => this.form = form} noValidate>
<Adaptive_Input
type="text"
name="name_input"
initial={'Name Input'}
focused={'Name Input'}
on_Input_Change={this.handle_text_input.bind(this)} />
<FieldFeedbacks for="name_input">
<FieldFeedback when="*" error />
<FieldFeedback when={value => !/^\w+$/.test(value)} warning>Should only contain alphanumeric characters</FieldFeedback>
</FieldFeedbacks>
<Adaptive_Input
type="number"
name="value_1_input"
initial={'Value 1'}
focused={'Value 1'}
on_Input_Change={this.handle_value_1_input.bind(this)} />
<FieldFeedbacks for="value_1_input">
<FieldFeedback when="*" />
</FieldFeedbacks>
<Adaptive_Input
type="number"
name="value_2_input"
initial={'Value 2'}
focused={'Value 2'}
on_Input_Change={this.handle_value_2_input.bind(this)} />
<FieldFeedbacks for="value_2_input">
<FieldFeedback when="*" />
</FieldFeedbacks>
</FormWithConstraints>
</div>
);
}
}
ReactDOM.render(<Form />, document.getElementById('app'));
The proposed solution here is hackish as I've tried to keep it close to the original jsfiddle. For proper form validation with react-form-with-constraints, check https://github.com/tkrotoff/react-form-with-constraints#examples
I have written This library which allows you to wrap your form element components, and lets you define your validators in the format :-
<Validation group="myGroup1"
validators={[
{
validator: (val) => !validator.isEmpty(val),
errorMessage: "Cannot be left empty"
},...
}]}>
<TextField value={this.state.value}
className={styles.inputStyles}
onChange={
(evt)=>{
console.log("you have typed: ", evt.target.value);
}
}/>
</Validation>
Use onChange={this.handleChange.bind(this, "name")
method and value={this.state.fields["name"]}
on input text field and below that create span element to show error, see the below example.
export default class Form extends Component {
constructor(){
super()
this.state ={
fields: {
name:'',
email: '',
message: ''
},
errors: {},
disabled : false
}
}
handleValidation(){
let fields = this.state.fields;
let errors = {};
let formIsValid = true;
if(!fields["name"]){
formIsValid = false;
errors["name"] = "Name field cannot be empty";
}
if(typeof fields["name"] !== "undefined" && !fields["name"] === false){
if(!fields["name"].match(/^[a-zA-Z]+$/)){
formIsValid = false;
errors["name"] = "Only letters";
}
}
if(!fields["email"]){
formIsValid = false;
errors["email"] = "Email field cannot be empty";
}
if(typeof fields["email"] !== "undefined" && !fields["email"] === false){
let lastAtPos = fields["email"].lastIndexOf('@');
let lastDotPos = fields["email"].lastIndexOf('.');
if (!(lastAtPos < lastDotPos && lastAtPos > 0 && fields["email"].indexOf('@@') === -1 && lastDotPos > 2 && (fields["email"].length - lastDotPos) > 2)) {
formIsValid = false;
errors["email"] = "Email is not valid";
}
}
if(!fields["message"]){
formIsValid = false;
errors["message"] = " Message field cannot be empty";
}
this.setState({errors: errors});
return formIsValid;
}
handleChange(field, e){
let fields = this.state.fields;
fields[field] = e.target.value;
this.setState({fields});
}
handleSubmit(e){
e.preventDefault();
if(this.handleValidation()){
console.log('validation successful')
}else{
console.log('validation failed')
}
}
render(){
return (
<form onSubmit={this.handleSubmit.bind(this)} method="POST">
<div className="row">
<div className="col-25">
<label htmlFor="name">Name</label>
</div>
<div className="col-75">
<input type="text" placeholder="Enter Name" refs="name" onChange={this.handleChange.bind(this, "name")} value={this.state.fields["name"]}/>
<span style={{color: "red"}}>{this.state.errors["name"]}</span>
</div>
</div>
<div className="row">
<div className="col-25">
<label htmlFor="exampleInputEmail1">Email address</label>
</div>
<div className="col-75">
<input type="email" placeholder="Enter Email" refs="email" aria-describedby="emailHelp" onChange={this.handleChange.bind(this, "email")} value={this.state.fields["email"]}/>
<span style={{color: "red"}}>{this.state.errors["email"]}</span>
</div>
</div>
<div className="row">
<div className="col-25">
<label htmlFor="message">Message</label>
</div>
<div className="col-75">
<textarea type="text" placeholder="Enter Message" rows="5" refs="message" onChange={this.handleChange.bind(this, "message")} value={this.state.fields["message"]}></textarea>
<span style={{color: "red"}}>{this.state.errors["message"]}</span>
</div>
</div>
<div className="row">
<button type="submit" disabled={this.state.disabled}>{this.state.disabled ? 'Sending...' : 'Send'}</button>
</div>
</form>
)
}
}
I have used redux-form and formik in the past, and recently React introduced Hook, and i have built a custom hook for it. Please check it out and see if it make your form validation much easier.
Github: https://github.com/bluebill1049/react-hook-form
Website: http://react-hook-form.now.sh
with this approach, you are no longer doing controlled input too.
example below:
import React from 'react'
import useForm from 'react-hook-form'
function App() {
const { register, handleSubmit, errors } = useForm() // initialise the hook
const onSubmit = (data) => { console.log(data) } // callback when validation pass
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input name="firstname" ref={register} /> {/* register an input */}
<input name="lastname" ref={register({ required: true })} /> {/* apply required validation */}
{errors.lastname && 'Last name is required.'} {/* error message */}
<input name="age" ref={register({ pattern: /\d+/ })} /> {/* apply a Refex validation */}
{errors.age && 'Please enter number for age.'} {/* error message */}
<input type="submit" />
</form>
)
}
Sometimes you can have multiple fields with similar validation in your application. In such a case I recommend to create common component field where you keep this validation.
For instance, let's assume that you have mandatory text input in a few places in your application. You can create a TextInput component:
constructor(props) {
super(props);
this.state = {
touched: false, error: '', class: '', value: ''
}
}
onValueChanged = (event) => {
let [error, validClass, value] = ["", "", event.target.value];
[error, validClass] = (!value && this.props.required) ?
["Value cannot be empty", "is-invalid"] : ["", "is-valid"]
this.props.onChange({value: value, error: error});
this.setState({
touched: true,
error: error,
class: validClass,
value: value
})
}
render() {
return (
<div>
<input type="text"
value={this.props.value}
onChange={this.onValueChanged}
className={"form-control " + this.state.class}
id="{this.props.id}"
placeholder={this.props.placeholder} />
{this.state.error ?
<div className="invalid-feedback">
{this.state.error}
</div> : null
}
</div>
)
}
And then you can use such a component anywhere in your application:
constructor(props) {
super(props);
this.state = {
user: {firstName: '', lastName: ''},
formState: {
firstName: { error: '' },
lastName: { error: '' }
}
}
}
onFirstNameChange = (model) => {
let user = this.state.user;
user.firstName = model.value;
this.setState({
user: user,
formState: {...this.state.formState, firstName: { error: model.error }}
})
}
onLastNameChange = (model) => {
let user = this.state.user;
user.lastName = model.value;
this.setState({
user: user,
formState: {...this.state.formState, lastName: { error: model.error }}
})
}
onSubmit = (e) => {
// submit logic
}
render() {
return (
<form onSubmit={this.onSubmit}>
<TextInput id="input_firstName"
value={this.state.user.firstName}
onChange={this.onFirstNameChange}
required = {true}
placeholder="First name" />
<TextInput id="input_lastName"
value={this.state.user.lastName}
onChange={this.onLastNameChange}
required = {true}
placeholder="Last name" />
{this.state.formState.firstName.error || this.state.formState.lastName.error ?
<button type="submit" disabled className="btn btn-primary margin-left disabled">Save</button>
: <button type="submit" className="btn btn-primary margin-left">Save</button>
}
</form>
)
}
Benefits:
Ref. https://webfellas.tech/#/article/5
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With