accounts-frontend/src/components/ui/form/Form.js

172 lines
4.4 KiB
JavaScript
Raw Normal View History

// @flow
import type { Node } from 'react';
import React, { Component } from 'react';
2016-05-02 12:45:42 +05:30
import classNames from 'classnames';
2017-06-13 01:02:59 +05:30
import logger from 'services/logger';
import type FormModel from './FormModel';
import styles from './form.scss';
type Props = {|
id: string,
isLoading: bool,
form?: FormModel,
onSubmit: (form: FormModel | FormData) => void | Promise<void>,
onInvalid: (errors: {[errorKey: string]: string}) => void,
children: Node
|};
2017-08-23 02:01:41 +05:30
type State = {
isTouched: bool,
isLoading: bool
};
type InputElement = HTMLInputElement | HTMLTextAreaElement;
2017-08-23 02:01:41 +05:30
export default class Form extends Component<Props, State> {
2016-05-02 12:45:42 +05:30
static defaultProps = {
id: 'default',
isLoading: false,
onSubmit() {},
onInvalid() {}
};
state = {
2016-05-28 01:34:17 +05:30
isTouched: false,
isLoading: this.props.isLoading || false
2016-05-02 12:45:42 +05:30
};
formEl: ?HTMLFormElement;
2018-05-08 00:53:26 +05:30
mounted = false;
componentDidMount() {
2016-05-28 01:34:17 +05:30
if (this.props.form) {
this.props.form.addLoadingListener(this.onLoading);
}
2018-05-08 00:53:26 +05:30
this.mounted = true;
2016-05-28 01:34:17 +05:30
}
componentWillReceiveProps(nextProps: Props) {
2016-05-02 12:45:42 +05:30
if (nextProps.id !== this.props.id) {
this.setState({
isTouched: false
});
}
2016-05-28 01:34:17 +05:30
2016-08-14 14:31:04 +05:30
if (typeof nextProps.isLoading !== 'undefined'
&& nextProps.isLoading !== this.state.isLoading
) {
2016-05-28 01:34:17 +05:30
this.setState({
isLoading: nextProps.isLoading
});
}
const nextForm = nextProps.form;
if (nextForm
&& this.props.form
&& nextForm !== this.props.form
) {
this.props.form.removeLoadingListener(this.onLoading);
nextForm.addLoadingListener(this.onLoading);
2016-05-28 01:34:17 +05:30
}
}
componentWillUnmount() {
if (this.props.form) {
this.props.form.removeLoadingListener(this.onLoading);
}
2018-05-08 00:53:26 +05:30
this.mounted = false;
2016-05-02 12:45:42 +05:30
}
render() {
2016-05-28 01:34:17 +05:30
const {isLoading} = this.state;
2016-05-02 12:45:42 +05:30
return (
<form
className={classNames(
styles.form,
{
[styles.isFormLoading]: isLoading,
[styles.formTouched]: this.state.isTouched
}
)}
onSubmit={this.onFormSubmit}
ref={(el: ?HTMLFormElement) => this.formEl = el}
2016-05-02 12:45:42 +05:30
noValidate
>
{this.props.children}
</form>
);
}
submit() {
2016-05-02 12:45:42 +05:30
if (!this.state.isTouched) {
this.setState({
isTouched: true
});
}
const form = this.formEl;
if (!form) {
return;
}
2016-05-02 12:45:42 +05:30
if (form.checkValidity()) {
const result = this.props.onSubmit(
this.props.form ? this.props.form : new FormData(form)
);
if (result && result.then) {
this.setState({isLoading: true});
result.catch((errors: {[key: string]: string}) => {
this.setErrors(errors);
2018-05-08 00:53:26 +05:30
}).finally(() =>
this.mounted && this.setState({isLoading: false})
);
}
2016-05-02 12:45:42 +05:30
} else {
const invalidEls: NodeList<InputElement> = (form.querySelectorAll(':invalid'): any);
const errors = {};
invalidEls[0].focus(); // focus on first error
Array.from(invalidEls).reduce((errors, el: InputElement) => {
if (!el.name) {
2017-06-13 01:02:59 +05:30
logger.warn('Found an element without name', {el});
return errors;
}
let errorMessage = el.validationMessage;
2017-09-09 19:52:19 +05:30
if (el.validity.valueMissing) {
errorMessage = `error.${el.name}_required`;
} else if (el.validity.typeMismatch) {
errorMessage = `error.${el.name}_invalid`;
}
errors[el.name] = errorMessage;
2016-05-02 12:45:42 +05:30
return errors;
}, errors);
2016-05-02 12:45:42 +05:30
this.setErrors(errors);
2016-05-02 12:45:42 +05:30
}
}
setErrors(errors: {[key: string]: string}) {
this.props.form && this.props.form.setErrors(errors);
this.props.onInvalid(errors);
}
onFormSubmit = (event: Event) => {
event.preventDefault();
this.submit();
2016-05-02 12:45:42 +05:30
};
2016-05-28 01:34:17 +05:30
onLoading = (isLoading: bool) => this.setState({isLoading});
2016-05-02 12:45:42 +05:30
}