Javascript 为什么我的表单输入在重新渲染后只注册一个字符?
在初始渲染时,输入组件将正常工作。然而,当它不再聚焦和重新聚焦后,它将只接受一个字符。如果我重置表单,也会发生这种情况 我创建了一个FormValidationStateManager类来重用所需的Joi验证逻辑 Form.jsxJavascript 为什么我的表单输入在重新渲染后只注册一个字符?,javascript,reactjs,input,frontend,Javascript,Reactjs,Input,Frontend,在初始渲染时,输入组件将正常工作。然而,当它不再聚焦和重新聚焦后,它将只接受一个字符。如果我重置表单,也会发生这种情况 我创建了一个FormValidationStateManager类来重用所需的Joi验证逻辑 Form.jsx class Form extends Component { constructor(props) { super(props); this.handleSubmit = this.handleSubmit.bind(this); this
class Form extends Component {
constructor(props) {
super(props);
this.handleSubmit = this.handleSubmit.bind(this);
this.validation = new FormValidationStateManager();
}
handleSubmit(e) {
e.preventDefault();
var result = this.validation.validateAll();
this.setState({
// need to do this, trigger render after validations
// we could eventually encapsulate this logic into FormValidationSM.
submit: Date.now()
}, function() {
if (result.valid) {
this.props.onSubmit(result.data);
}
}.bind(this));
}
resetForm(e) {
e.preventDefault();
this.validation.clearForm();
this.setState({
// need to do this, trigger render after validations,
// we could eventually encapsulate this logic into FormValidationSM.
submit: Date.now()
});
}
render() {
// TODO implement logic for rendering name from API call
let name = "John Doe";
return (
<form class='info-form' onSubmit={this.handleSubmit} autocomplete='off'>
<div class='info-block-section-title'>
<h2>Welcome {name}</h2>
<h4>Some nicely worded few sentences that let's the user know they just need to fill out a few bits of information to start the process of getting their student loans paid down!</h4>
</div>
<br />
<div class='info-block-section-body'>
<InfoFieldset validation={this.validation} />
</div>
<div class='info-form-button'>
<FormButton label="Continue" />
<FormButton type="button" onClick={this.resetForm.bind(this)} label="Reset" />
</div>
</form>
)
}
}
export default Form;
class FormElementInput extends Component {
constructor(props) {
super(props);
this.input = React.createRef();
this.cachedErrors = {};
this.cachedValue = null;
this.state = { focused: false };
this.validation = this.props.validation || new FormValidationStateManager();
}
componentDidUpdate(prevProps, prevState) {
}
shouldComponentUpdate(nextProps, nextState) {
var filter = function(val, key, obj) {
if (_.isFunction(val)) {
return true;
}
// Certain props like "label" can take a React element, but those
// are created dynamically on the fly and they have an random internal UUID,
// which trips the deep-equality comparision with a false-positive.
// This is wasted cycles for rendering.
if (React.isValidElement(val)) {
return true;
}
// validation is a complex obj that we shouldn't be caring about
if (_.contains(['validation'], key)) {
return true;
}
};
// if the errors after a validation got updated, we should re-render.
// We have to compare in this manner because the validation object is not shallow-copied
// via props since it is managed at a higher-component level, so we need to
// do a local cache comparision with our local copy of errors.
if (!_.isEqual(this.cachedErrors, this.getErrors())) {
return true;
}
// if the errors after a validation got updated, we should re-render.
// We have to compare in this manner because the validation object is not shallow-copied
// via props since it is managed at a higher-component level, so we need to
// do a local cache comparision with our local copy of errors.
if (this.cachedValue !== this.getDisplayValue()) {
return true;
}
return !_.isEqual(_.omit(nextProps, filter), _.omit(this.props, filter)) || !_.isEqual(this.state, nextState);
}
setValue(value) {
console.error('deprecated - can not set value directly on FormElementInput anymore');
throw 'deprecated - can not set value directly on FormElementInput anymore';
}
isDollarType() {
return this.props.type === 'dollar';
}
isRateType() {
return this.props.type === 'rate';
}
getType() {
if (this.isDollarType() || this.isRateType()) {
return 'text';
} else {
return this.props.type;
}
}
getPrefix() {
if (this.isDollarType()) {
return _.compact([this.props.prefix, '$']).join(' ');
} else {
return this.props.prefix;
}
}
getPostfix() {
if (this.isRateType()) {
return _.compact([this.props.postfix, '%']).join(' ');
} else {
return this.props.postfix;
}
}
getDisplayValue() {
var value = this.props.value || this.validation.getFormValue(this.props.field);
// while DOM input is focused, just return the same value being typed
if (this.state.focused) {
return value;
}
if (this.isDollarType()) {
if (_.isUndefined(value)) {
return value;
}
// keep in sync with the validation check in getOnChange()
// even if this is different - it wont break - cause accounting.js handles gracefully
else if (_.isNumber(value) || !value.match(/[a-z]/i)) {
return accounting.formatMoney(value, '', 2);
} else {
return value;
}
} else if (this.isRateType()){
if (_.isUndefined(value)) {
return '';
}
return accounting.formatNumber(value, 3)
} else {
return value;
}
}
getErrors() {
return this.props.errors || this.validation.getValidationMessages(this.props.field);
}
onBlur(event) {
this.validation.validate(this.props.field);
this.setState({ focused: false });
}
onFocus(event) {
this.setState({ focused: true });
}
onChange(event) {
if (this.isDollarType()) {
// only accept if the input at least resembles a currency
// accounting.parse already strips alot of characters and does this silently
// we want to be a little less strict than accounting.parse since we need to allow more
// different types of inputs +-,. and also let accounting.parse do its job
// very basic check here for a-z characters
if (!event.target.value.match(/[a-z]/i)) {
var parsedValue = accounting.parse(event.target.value);
event._parsedValue = parsedValue;
}
}
let _value = event._parsedValue || event.target.value;
console.log(this.input)
this.validation.setFormValue(this.props.field, _value);
}
_eventHandlers(field) {
return {
onChange: this.onChange.bind(this),
onBlur: this.onBlur.bind(this),
onFocus: this.onFocus.bind(this)
};
}
_mergeEventHandlers(p1, p2) {
var events = [
'onChange',
'onBlur',
'onFocus'
];
var r1 = {};
events.map(function(e) {
r1[e] = FuncUtils.mergeFunctions(p1[e], p2[e]);
}.bind(this));
return r1;
}
render() {
let _errors = this.cachedErrors = this.getErrors();
let _value = this.cachedValue = this.getDisplayValue();
let attrs = {
id: this.props.htmlId || ('field_' + this.props.field),
className: classnames({
'error': _errors && _errors.length
}),
type: this.getType(),
placeholder: this.props.placeholder,
autoComplete: "false",
disabled: this.props.disabled,
readOnly: this.props.disabled,
value: _value,
ref: this.input,
};
attrs = _.extend(attrs, this._mergeEventHandlers(this.props, this._eventHandlers()));
const prefix = this.getPrefix();
const postfix = this.getPostfix();
return (
<FormElementWrapper
type="input"
field={this.props.field}
errors={_errors}
label={this.props.label}>
<div className="form-element-input">
<DisplayToggle remove={true} hide={!prefix}>
<span className="prefix">{prefix}</span>
</DisplayToggle>
<div className={classnames({
"has-prefix": !!prefix,
"has-postfix": !!postfix
})}>
<input {...attrs} />
</div>
<DisplayToggle remove={true} hide={!postfix}>
<span className="postfix">{postfix}</span>
</DisplayToggle>
</div>
<div className="form-error">
{_errors.map((msg, idx) => {
return (<FormError key={idx}>{msg}</FormError>)
})}
</div>
</FormElementWrapper>
)
}
};
FormElementInput.PropTypes = {
field: PropTypes.string.isRequired,
id: PropTypes.string.isRequired,
value: PropTypes.any,
label: PropTypes.any,
errors: PropTypes.array,
type: PropTypes.string,
placeholder: PropTypes.string,
onChange: PropTypes.func,
onFocus: PropTypes.func,
onKeyUp: PropTypes.func,
tooltip: PropTypes.string,
prefix: PropTypes.string,
postfix: PropTypes.string,
disabled: PropTypes.bool,
disableTrimming: PropTypes.bool
}
FormElementInput.defaultProps = {
type: 'text',
placeholder: 'Enter Value',
tooltip: undefined,
prefix: undefined,
postfix: undefined,
disabled: false,
disableTrimming: false
}
export default FormElementInput;
我想我明白了。我将FormElementInput.jsx中的属性的
value:_value
更改为defaultValue:_value
class Form extends Component {
constructor(props) {
super(props);
this.handleSubmit = this.handleSubmit.bind(this);
this.validation = new FormValidationStateManager();
}
handleSubmit(e) {
e.preventDefault();
var result = this.validation.validateAll();
this.setState({
// need to do this, trigger render after validations
// we could eventually encapsulate this logic into FormValidationSM.
submit: Date.now()
}, function() {
if (result.valid) {
this.props.onSubmit(result.data);
}
}.bind(this));
}
resetForm(e) {
e.preventDefault();
this.validation.clearForm();
this.setState({
// need to do this, trigger render after validations,
// we could eventually encapsulate this logic into FormValidationSM.
submit: Date.now()
});
}
render() {
// TODO implement logic for rendering name from API call
let name = "John Doe";
return (
<form class='info-form' onSubmit={this.handleSubmit} autocomplete='off'>
<div class='info-block-section-title'>
<h2>Welcome {name}</h2>
<h4>Some nicely worded few sentences that let's the user know they just need to fill out a few bits of information to start the process of getting their student loans paid down!</h4>
</div>
<br />
<div class='info-block-section-body'>
<InfoFieldset validation={this.validation} />
</div>
<div class='info-form-button'>
<FormButton label="Continue" />
<FormButton type="button" onClick={this.resetForm.bind(this)} label="Reset" />
</div>
</form>
)
}
}
export default Form;
class FormElementInput extends Component {
constructor(props) {
super(props);
this.input = React.createRef();
this.cachedErrors = {};
this.cachedValue = null;
this.state = { focused: false };
this.validation = this.props.validation || new FormValidationStateManager();
}
componentDidUpdate(prevProps, prevState) {
}
shouldComponentUpdate(nextProps, nextState) {
var filter = function(val, key, obj) {
if (_.isFunction(val)) {
return true;
}
// Certain props like "label" can take a React element, but those
// are created dynamically on the fly and they have an random internal UUID,
// which trips the deep-equality comparision with a false-positive.
// This is wasted cycles for rendering.
if (React.isValidElement(val)) {
return true;
}
// validation is a complex obj that we shouldn't be caring about
if (_.contains(['validation'], key)) {
return true;
}
};
// if the errors after a validation got updated, we should re-render.
// We have to compare in this manner because the validation object is not shallow-copied
// via props since it is managed at a higher-component level, so we need to
// do a local cache comparision with our local copy of errors.
if (!_.isEqual(this.cachedErrors, this.getErrors())) {
return true;
}
// if the errors after a validation got updated, we should re-render.
// We have to compare in this manner because the validation object is not shallow-copied
// via props since it is managed at a higher-component level, so we need to
// do a local cache comparision with our local copy of errors.
if (this.cachedValue !== this.getDisplayValue()) {
return true;
}
return !_.isEqual(_.omit(nextProps, filter), _.omit(this.props, filter)) || !_.isEqual(this.state, nextState);
}
setValue(value) {
console.error('deprecated - can not set value directly on FormElementInput anymore');
throw 'deprecated - can not set value directly on FormElementInput anymore';
}
isDollarType() {
return this.props.type === 'dollar';
}
isRateType() {
return this.props.type === 'rate';
}
getType() {
if (this.isDollarType() || this.isRateType()) {
return 'text';
} else {
return this.props.type;
}
}
getPrefix() {
if (this.isDollarType()) {
return _.compact([this.props.prefix, '$']).join(' ');
} else {
return this.props.prefix;
}
}
getPostfix() {
if (this.isRateType()) {
return _.compact([this.props.postfix, '%']).join(' ');
} else {
return this.props.postfix;
}
}
getDisplayValue() {
var value = this.props.value || this.validation.getFormValue(this.props.field);
// while DOM input is focused, just return the same value being typed
if (this.state.focused) {
return value;
}
if (this.isDollarType()) {
if (_.isUndefined(value)) {
return value;
}
// keep in sync with the validation check in getOnChange()
// even if this is different - it wont break - cause accounting.js handles gracefully
else if (_.isNumber(value) || !value.match(/[a-z]/i)) {
return accounting.formatMoney(value, '', 2);
} else {
return value;
}
} else if (this.isRateType()){
if (_.isUndefined(value)) {
return '';
}
return accounting.formatNumber(value, 3)
} else {
return value;
}
}
getErrors() {
return this.props.errors || this.validation.getValidationMessages(this.props.field);
}
onBlur(event) {
this.validation.validate(this.props.field);
this.setState({ focused: false });
}
onFocus(event) {
this.setState({ focused: true });
}
onChange(event) {
if (this.isDollarType()) {
// only accept if the input at least resembles a currency
// accounting.parse already strips alot of characters and does this silently
// we want to be a little less strict than accounting.parse since we need to allow more
// different types of inputs +-,. and also let accounting.parse do its job
// very basic check here for a-z characters
if (!event.target.value.match(/[a-z]/i)) {
var parsedValue = accounting.parse(event.target.value);
event._parsedValue = parsedValue;
}
}
let _value = event._parsedValue || event.target.value;
console.log(this.input)
this.validation.setFormValue(this.props.field, _value);
}
_eventHandlers(field) {
return {
onChange: this.onChange.bind(this),
onBlur: this.onBlur.bind(this),
onFocus: this.onFocus.bind(this)
};
}
_mergeEventHandlers(p1, p2) {
var events = [
'onChange',
'onBlur',
'onFocus'
];
var r1 = {};
events.map(function(e) {
r1[e] = FuncUtils.mergeFunctions(p1[e], p2[e]);
}.bind(this));
return r1;
}
render() {
let _errors = this.cachedErrors = this.getErrors();
let _value = this.cachedValue = this.getDisplayValue();
let attrs = {
id: this.props.htmlId || ('field_' + this.props.field),
className: classnames({
'error': _errors && _errors.length
}),
type: this.getType(),
placeholder: this.props.placeholder,
autoComplete: "false",
disabled: this.props.disabled,
readOnly: this.props.disabled,
value: _value,
ref: this.input,
};
attrs = _.extend(attrs, this._mergeEventHandlers(this.props, this._eventHandlers()));
const prefix = this.getPrefix();
const postfix = this.getPostfix();
return (
<FormElementWrapper
type="input"
field={this.props.field}
errors={_errors}
label={this.props.label}>
<div className="form-element-input">
<DisplayToggle remove={true} hide={!prefix}>
<span className="prefix">{prefix}</span>
</DisplayToggle>
<div className={classnames({
"has-prefix": !!prefix,
"has-postfix": !!postfix
})}>
<input {...attrs} />
</div>
<DisplayToggle remove={true} hide={!postfix}>
<span className="postfix">{postfix}</span>
</DisplayToggle>
</div>
<div className="form-error">
{_errors.map((msg, idx) => {
return (<FormError key={idx}>{msg}</FormError>)
})}
</div>
</FormElementWrapper>
)
}
};
FormElementInput.PropTypes = {
field: PropTypes.string.isRequired,
id: PropTypes.string.isRequired,
value: PropTypes.any,
label: PropTypes.any,
errors: PropTypes.array,
type: PropTypes.string,
placeholder: PropTypes.string,
onChange: PropTypes.func,
onFocus: PropTypes.func,
onKeyUp: PropTypes.func,
tooltip: PropTypes.string,
prefix: PropTypes.string,
postfix: PropTypes.string,
disabled: PropTypes.bool,
disableTrimming: PropTypes.bool
}
FormElementInput.defaultProps = {
type: 'text',
placeholder: 'Enter Value',
tooltip: undefined,
prefix: undefined,
postfix: undefined,
disabled: false,
disableTrimming: false
}
export default FormElementInput;
看到了吗
有人知道为什么要把它固定在引擎盖下吗