重复点击按钮会导致重复提交表单,从而产生重复的数据。我们可以有多种方式解决,比如在数据库添加唯一索引。在这里,提供另一种方式,在前端控制用户重复点击。
前端用的是react + antd,因此案例代码与react相关,但思想是一致的。
提供公共组件:
定义了state: buttonDisabled, updateButtonDisable function
import React from 'react';
import { Button } from 'antd';
import PropTypes from 'prop-types';
/**
* This component is created to implement the requirement
* disabling submit button until the request is completed
*/
class SubmitButton extends React.Component {
state = {
buttonDisabled: this.props.initialDisabledStatus,
};
componentDidUpdate(prevProps) {
const { initialDisabledStatus } = this.props;
if (prevProps.initialDisabledStatus !== initialDisabledStatus) {
this.updateButtonDisabled(initialDisabledStatus);
}
}
updateButtonDisabled = (disable) => {
this.setState({ buttonDisabled: disable });
}
render() {
const { buttonDisabled } = this.state;
const { initialDisabledStatus, ...rest } = this.props;
return (
<Button
{...rest}
style={buttonDisabled ? { cursor: 'progress' } : null}
disabled={buttonDisabled}
onClick={e => this.props.onClick(e)}
>
{this.props.children}
</Button>
);
}
}
SubmitButton.defaultProps = {
initialDisabledStatus: false,
};
SubmitButton.propTypes = {
onClick: PropTypes.func.isRequired,
initialDisabledStatus: PropTypes.bool,
};
export default SubmitButton;
引用SubmitButton组件, 添加ref,这是为了获取SubmitButton组件实例。
在handleSubmit函数中,将this.submitButton.updateButtonDisabled传给sendmail函数
handleSubmit = () => {
const { form, actions } = this.props;
form.validateFields((err, values) => {
if (!err) {
actions.sendEmail(values.email, this.submitButton.updateButtonDisabled);
}
});
}
<Form>
<Form.Item>
<SubmitButton ref={(e) => { this.submitButton = e; }} onClick={this.handleSubmit}>Submit</SubmitButton>
</Form.Item>
</Form>
在发送请求之前,call setSubmitButtonDisabled(true)去disable button,当请求结束或发生错误时,再 call setSubmitButtonDisabled(false)使按钮又可以点击。
export const sendEmail = (emailAddress, setSubmitButtonDisabled) => ((dispatch) => {
setSubmitButtonDisabled(true);
createResource('/api/account/password/reset', { email: emailAddress }).then(() => {
setSubmitButtonDisabled(false);
history.push('/account/password/reset/done');
}).catch((err) => {
setSubmitButtonDisabled(false);
dispatch(setErrorMsgAction(true, err.entity.message));
});
});
以上案例是基本案例。但有时我们会遇到如下情况:
对表单中的某个字段进行后端校验,如果存在就不能创建,反之就可以创建。如果是这种情况,上述案例就不能控制住,因为这时有校验和创建两个异步的action,难以控制,因此,我采用了另一种方式,基于方式一的基础上:
此案例:
实现对assement的复制,名字不能重复。
AppModal和SubmitButton本质是一样的
import React from 'react';
import PropTypes from 'prop-types';
import { Modal } from 'antd';
import classnames from 'classnames';
class AppModal extends React.Component {
state = {
buttonDisabled: false,
};
updateButtonDisabled = (disable) => {
this.setState({ buttonDisabled: disable });
}
render() {
const { buttonDisabled } = this.state;
return (
<Modal
{...this.props}
maskClosable={false}
wrapClassName={classnames(`common-modal-style ${this.props.wrapClassName}`, { 'submit-disabled': buttonDisabled })}
maskStyle={{ backgroundColor: 'rgba(240, 242, 245, 0.8)' }}
okButtonProps={{
disabled: buttonDisabled ||
(this.props.okButtonProps && this.props.okButtonProps.disabled),
}}
onOk={e => this.props.onOk(e)}
>
{this.props.children}
</Modal>
);
}
}
AppModal.defaultProps = {
title: ' ', // if title is not set, the style will be broken
destroyOnClose: true,
visible: false,
};
AppModal.propTypes = {
title: PropTypes.oneOfType([PropTypes.string, PropTypes.node]),
destroyOnClose: PropTypes.bool,
visible: PropTypes.bool,
onCancel: PropTypes.func.isRequired,
children: PropTypes.node.isRequired,
};
export default AppModal;
定义了state: {fieldsHasError: true, assessmentDuplicate: true, assessmentName: ''}, assessmentValidateCallBack是当后端校验有错误时的回调函数,更新state的值。
这里,提交按钮绑定的函数是duplicateAssessment,该函数实际上调用的是actions.checkIfAssessmentAlreadyExists,用来校验name是否重复,真正执行创建功能是再componentDidUpdate周期里,当!fieldsHasError && !assessmentDuplicate 为true时,才真正的复制assessment。
import React from 'react';
import AppModal from 'App/Common/Modal/Components/AppModal';
import { Form, Input } from 'antd';
import PropTypes from 'prop-types';
import { FIELD_MAX_LENGTH } from 'App/Common/Constants';
class DuplicateModal extends React.Component {
state = {
fieldsHasError: true,
assessmentDuplicate: true,
assessmentName: '',
};
componentDidUpdate() {
const { fieldsHasError, assessmentDuplicate, assessmentName } = this.state;
const {
currentSelect, actions, pagination,
} = this.props;
const params = {
id: currentSelect.id,
name: assessmentName,
};
if (!fieldsHasError && !assessmentDuplicate) {
actions.duplicateAssessment(
params,
pagination.current,
this.appModal.updateButtonDisabled,
);
this.setState({
fieldsHasError: true,
assessmentDuplicate: true,
assessmentName: '',
});
}
}
onCancel= () => {
this.props.actions.duplicateModalVisibleSwitch(false);
}
assessmentValidateCallBack = (message) => {
const { form } = this.props;
if (message) {
form.setFields({
name:
{
value: form.getFieldValue('name'),
errors: [new Error(message)],
},
});
}
this.setState({ assessmentDuplicate: message !== undefined && message !== null });
};
duplicateAssessment = () => {
const {
form, actions,
} = this.props;
const fieldName = ['name'];
form.validateFieldsAndScroll(fieldName, (err, values) => {
if (!err) {
actions.checkIfAssessmentAlreadyExists(
values.name,
this.assessmentValidateCallBack,
this.appModal.updateButtonDisabled,
);
this.setState({ fieldsHasError: err, assessmentName: values.name });
}
});
};
render() {
const { form, visible } = this.props;
const { getFieldDecorator } = form;
return (
<AppModal ref={(e) => { this.appModal = e; }} title="Duplicate Assessment" visible={visible} onOk={this.duplicateAssessment} onCancel={this.onCancel}>
<Form>
<Form.Item label="New Assessment name">
{getFieldDecorator('name', {
rules: [{
required: true, message: 'Please enter a new assessment name', whitespace: true,
}],
})(<Input maxLength={FIELD_MAX_LENGTH.NAME} />)}
</Form.Item>
</Form>
</AppModal>
);
}
}
DuplicateModal.propTypes = {
form: PropTypes.shape().isRequired,
actions: PropTypes.shape().isRequired,
currentSelect: PropTypes.shape().isRequired,
pagination: PropTypes.shape().isRequired,
visible: PropTypes.bool.isRequired,
};
export default Form.create()(DuplicateModal);