Validation with Form Inputs in ReactJS

cubbuk
5 min readOct 12, 2016

--

In this article I will try to explain how to make form manipulation in React easier by using a stateful input controller.

Validation of forms in react is a bit cumbersome especially for someone used to easy form handling in AngularJS. With two-way data binding and form utilities of Angular, form validation is quite trivial. However that was not the case for me using React as you need to keep track of state of form inputs yourself.

For example you need to keep track of focused and touched state of your input field. In addition to that you need to keep the value of your state too. For a multi field value this turns into a lots of state variables which can turn parent component into a mess quite quickly.

I come up with an example of a stateful form input control which might be helpful while handling forms in ReactJS. The idea is quite simple, this is just an extended html input controller which holds its own state for focus (isFocused), value, and touched (isDirty) values. It uses props to let the parent component know about changed value of the input. And it also usescomponentWillReceiveProps event to update its state according to new props if that is ever required.

For showing validations this component depends on validate.js which is a very handy library for defining constraints on your modals. I also used React-Bootstrap for styling form inputs, but any other library or pure inputs can be used instead too.

An Example Usage

<FormInputWithValidation name="name"
label="Name"
type="text"
formControlClass={this.DEFAULT_FORM_CONTROL_CLASS}
formGroupClass={this.DEFAULT_FORM_GROUP_CLASS}
labelClass={this.DEFAULT_LABEL_CLASS}
formSubmitted={formSubmitted}
value={name}
validationFunction={(name) => validate({name}, userConstraints.name(), {fullMessages: false})}
onValueChange={this.onValueChange.bind(this, "name")}/>

There are many props here but the main props you should focus on are value, formSubmitted, validationFunctionand onValueChange. Props named formControlClass, formGroupClass and labelClass are merely used for stylingReact-Bootstrap Form Control component so they can be changed or omitted according to the requirements of your use case. And other props are attributes of html input controller.

value prop is used for initializing value of FormInputWithValidation component. validationFunction is a function which will be called by our component in each render method for displaying any errors if there exist any and the requirements for displaying errors are met. This component does not show any error unless formSubmitted prop is true which represents whether the form is submitted or not. In some cases such as update forms, formSubmitted can be initalized to true to see existing errors immediately. By using onValueChange prop parent component sychronize itself with the value of the component. onValueChange returns immediately the value of the input rather than event object, it also trims the result of the input if the type is text. If you need event object you can use default prop of input i.e.onValueChange too.

Details of FormInputWithValidation Component

Let’s get into details of our component which handles displaying validations for us. First thing we need to focus on is initializing our component and updating its state when the received props changes.

constructor(props, context, ...args) {
super(props, context, ...args);
let {value, isFocused, isDirty} = this.props;
this.state = {value, isFocused, isDirty};
}

componentWillReceiveProps(nextProps) {
let {value, isFocused, isDirty} = this.props;
let {value: nextValue, isFocused: nextIsFocused, isDirty: nextIsDirty} = nextProps;
if (value !== nextValue || isFocused !== nextIsFocused || isDirty !== nextIsDirty) {
let nextState = {};
if (value !== nextValue) {
nextState.value = nextValue;
}
if (value !== isFocused) {
nextState.isFocused = nextIsFocused;
}
if (value !== isDirty) {
nextState.isDirty = nextIsDirty;
}
this.setState(nextState);
}
}

In the constructor we initialize our state variables value, isDirty and isFocused. By usingcomponentWillReceiveProps event, we synchronize our state with new props. If the nextProps are the same with existing props we don't update the state. But if any of the new props is different from the existing ones, we initialize our state with the value of nextProps.

This is needed because after the initialization of our component, it has no knowledge about outside world. For example in case of a reset button in a form, the parent component will reset its modal object but the component would still use its own existing state value. And this would result in a divergence between the modal and the view. For synchronizing them we needcomponentWillReceiveProps which is called whenever the render method of parent component is called that eventually sends new props to this component. We simply update our state with new props if they are different from the last given props.

Lets check our render method:

render() {
let {value} = this.state;
let {formControlClass, formGroupClass, errorClass, defaultClass, onValueChange, formSubmitted, labelClass, validationFunction, name, ...otherProps} = this.props;
let validationMessages = this.validate(value);
return <FormGroup bsClass={formGroupClass}
onClick={this.focusToInput.bind(this)}
validationState={this.getValidationState(validationMessages)}>
{this.renderLabel(validationMessages)}
<FormControl name={name}
ref="inputNode"
bsClass={formControlClass}
onBlur={this.onInputBlurred.bind(this)}
onFocus={this.onInputFocused.bind(this)}
onChange={this.onInputChanged.bind(this)}
{...otherProps}/>
</FormGroup>
}

It uses the value of its state for input control. We extended FormControl using ...otherPropsby using rest parameters of ES6. We implemented our onBlur, onFocus and onChange event for this component and in these methods we updatevalue, isFocused, isDirty of our state. Let's take a look at onInputChanged method of our component:

onInputChanged(e) {
let value = this.eventToValue(e);
let {onChange, onValueChange} = this.props;
if (_.isFunction(onValueChange)) {
onValueChange(value);
}
if (_.isFunction(onChange)) {
onChange(e);
}
this.setState({value});
}

eventToValue(e, trimResults) {
let {target = {}} = e;
let {value = "", type = ""} = target;
if (type === "number" && value !== undefined && value !== null && value !== "") {
value = Number(value);
} else {
value = (value || "");
if (value && trimResults) {
value = value.trim();
}
}
return value;
}

As you can see from the eventToValue method, according to the type of the input we convert the input into a number or trim the string. This is not necessary but can be handy in your form manipulations as you don't want to send string to your backend service when a numeric field expected. The important part of this function here is that, it checks whether the parent component passedonValueChange or onChange props to this component and they pass the value or event to parent component through these props. By doing so even this component is holding its state on its own, the parent of this component can be aware of updated values.

For showing errors renderLabel and validate methods are used. In each render phase result of validate function is passed to renderLabel method which displays validation messages of the component. validate function uses thevalidationFunction passed to component through props. Validation messages are only displayed iferrorsCanBeDisplayed method returns true which checks whether the formSubmitted prop is true or the input is not focused but is dirty.

errorsCanBeDisplayed() {
let {isFocused, isDirty} = this.state;
let {formSubmitted} = this.props;
return (!isFocused && isDirty) || formSubmitted;
}

validate(value) {
let {validationFunction} = this.props;
let validationMessages = [];
if (_.isFunction(validationFunction)) {
let validationResult = validationFunction(value);
if (validationResult) {
Object.keys(validationResult).map(errorField => validationResult[errorField].map(validationMessage => validationMessages.push(validationMessage)));
}
}

return validationMessages;
}

renderLabel(validationMessages = []) {
let {labelClass, label} = this.props;
let subView = label;
if (this.errorsCanBeDisplayed()) {
if (validationMessages.length > 0) {
subView = validationMessages.map((validationMessage, index) => <span
key={index}>{validationMessage}</span>);
}
}
return <ControlLabel bsClass={labelClass}>{subView}</ControlLabel>
}

So by wrapping the state of input in a controlled component it is possible to have form manipulation with validation messages in React without having a gigantic parent controller. Also as each application has its own requirements you can change your view by using different styles or components in your input component, or by changing errorsCanBeDisplayed method you can change your displaying error policy.

What do you think of this approach, if you have any suggestions please let me know. You can find the example project from the following github repo too https://github.com/cubbuk/form_input_with_validation.

Cheers

--

--