Я пытаюсь, чтобы элементы ввода формы, которые не контролируются из-за нашего использования jQuery UI DatePicker и jQuery маскаДеньги, отображали ошибки под ними, как только пользователь вводил что-то недопустимое для этого поля, а также отключали кнопку на любой из ошибок. По какой-то причине ничего из этого не работает правильно.
Главный компонент
что-то вроде следующего:
class MainComponent extends React.Component {
constructor(props) {
super(props)
this.state = {
payrates: [
new PayRate(new Date(2019, 2, 1), 0.00),
],
errors : {
rate: '',
date: ''
},
currentPayRate : new PayRate() // has Rate and EffectiveDate fields
}
// binding done here
this.appendValue = this.appendValue.bind(this)
this.updateCurrentPayRate = this.updateCurrentPayRate.bind(this)
this.updateCurrentPayRateDate = this.updateCurrentPayRateDate.bind(this)
this.updateCurrentPayRateAmount = this.updateCurrentPayRateAmount.bind(this)
this.validate = this.validate.bind(this)
}
/**
* @param { PayRate } newPayRate
**/
updateCurrentPayRate(newPayRate) {
this.setState({
...this.state,
currentPayRate : newPayRate
})
}
updateCurrentPayRateDate(dateString) {
const newPayRate = Object.assign(new PayRate(), this.state.currentPayRate, { EffectiveDate : new Date(dateString) } )
this.validate(newPayRate)
this.updateCurrentPayRate(newPayRate)
}
updateCurrentPayRateAmount(amount) {
const newPayRate = Object.assign(new PayRate(), this.state.currentPayRate, { Rate : Number(amount) } )
this.validate(newPayRate)
this.updateCurrentPayRate(newPayRate)
}
/**
* @param { PayRate } value
**/
appendValue(value) {
console.info("trying to append value: ", value)
if (this.validate(value)) {
this.setState({...this.state,
payrates : this.state.payrates.concat(this.state.currentPayRate)})
}
}
/**
* @param { PayRate } value
**/
validate(value) {
// extract rate,date from value
const rate = value.Rate,
date = value.EffectiveDate
console.info("value == ", value)
let errors = {}
// rate better resolve to something
if (!rate) {
errors.rate = "Enter a valid pay rate amount"
}
// date better be valid
if ((!date) || (!date.toLocaleDateString)) {
errors.date = "Enter a date"
}
else if (date.toLocaleDateString("en-US") === "Invalid Date") {
errors.date = "Enter a valid pay rate date"
}
console.info(errors)
// update the state with the errors
this.setState({
...this.state,
errors : errors
})
const errorsToArray = Object.values(errors).filter((error) => error)
return !errorsToArray.length;
}
render() {
return <div>
<DateList dates = {this.state.payrates}/>
<NewPayRateRow
value = {this.state.currentPayRate}
errors = {this.state.errors}
onChange = {this.updateCurrentPayRate}
onPayRateAmountChange = {this.updateCurrentPayRateAmount}
onPayRateDateChange = {this.updateCurrentPayRateDate}
onAdd = {this.appendValue}
/>
</div>
}
}
Компонент «форма»
Имеет следующую реализацию:
class NewPayRateRow extends React.Component {
constructor(props) {
super(props)
}
render() {
console.info(Object.values(this.props.errors).filter((error) => error))
return <span class = "form-inline">
<RateField
errors = {this.props.errors.rate}
onKeyUp = {(e) => {
// extract the value
const value = e.target.value
this.props.onPayRateAmountChange(value)
}}
/>
<DateInput
errors = {this.props.errors.date}
onChange = {this.props.onPayRateDateChange}
/>
<button onClick = {(e) => {
this.props.onAdd(this.props.value)
}}
disabled = {Object.values(this.props.errors).filter((error) => error).length}>Add New Pay Rate</button>
</span>
}
}
Неконтролируемый входной компонент
где проблема определенно возникает:
class DateInput extends React.Component {
constructor(props) {
super(props);
// do bindings
this.handleChangeEvent = this.handleChangeEvent.bind(this);
}
componentDidMount() {
$('#datepicker').datepicker({
changeMonth: true,
changeYear: true,
showButtonPanel: true,
yearRange: "-116:+34",
dateFormat: 'mm/dd/yy',
// telling jQuery UI to pass its event to React
onSelect : this.handleChangeEvent
});
}
componentWillUnmount() {
$('#datepicker').datepicker('destroy')
}
// handles a change to the input field
handleChangeEvent(value) {
this.props.onChange(value)
}
render() {
const fieldIsInvalid = this.props.errors || ''
return <div class = "col-md-2">
<input
id = "datepicker"
className = {"datepicker form-control " + fieldIsInvalid }
placeholder = "mm/dd/yyyy"
onChange = {(e) => this.props.onChange(e.target.value) }>
</input>
<div>
{this.props.errors}
</div>
</div>
}
}
По какой-то причине, хотя я выбираю через виджет выбора даты значение, errors не меняется:
Однако, когда я закомментирую все вызовы validate, поля добавляются без проблем.
Я провел некоторую отладку пещерного человека на value, который я передал validate, чтобы убедиться, что я передал ему правдивые данные.
Почему this.state.error не обновляется корректно через компоненты?!
ОБНОВИТЬ: Сначала я обновил только ставку оплаты, и ошибки отобразились правильно, и, просмотрев код, я обнаружил, что this.setState фактически устанавливал состояние. Однако, когда я пошел, чтобы вызвать изменение в поле ввода денег, this.setState получал удар, а errors объект был пуст (что правильно), но каким-то образом this.setState на самом деле не обновлял состояние.



![Безумие обратных вызовов в javascript [JS]](https://i.imgur.com/WsjO6zJb.png)


У вас есть атрибут «класс» внутри нескольких ваших функций рендеринга, замена его на «имя класса» позволит отобразить ошибку: https://codepen.io/BPagoaga/pen/QoMXmw
return <div className = "col-md-2">
Я исправил проблему!
Что я сделал
Вместо того, чтобы сохранять errors в глобальном state и вместо передачи validate для установки глобального состояния в методы, я поддерживаю его как функцию, определенную вне класса основного компонента, например:
/**
* Validates a PayRate
* @param { PayRate } value
* @returns { Object } any errors
**/
function validate(value = {}) {
// extract rate,date from value
const rate = value.Rate,
date = value.EffectiveDate
let errors = {}
// rate better resolve to something
if (!rate) {
errors.rate = "Enter a valid pay rate amount"
}
// date better be valid
if ((!date) || (!date.toLocaleDateString)) {
errors.date = "Enter a date"
}
else if (date.toLocaleDateString("en-US") === "Invalid Date") {
errors.date = "Enter a valid pay rate date"
}
return errors
}
Обратите внимание на гораздо более простую реализацию. После этого мне больше не нужно вызывать validate для updateCurrentPayRate... методов.
Вместо этого я вызываю его для NewPayRateRow.render (что теперь я могу сделать, потому что он вообще не касается состояния, избегая любого нарушения инварианта), сохраняю результат в локальную константную переменную с именем errors и использую ее вместо this.props.errors. Хотя, по правде говоря, я, вероятно, мог бы вернуть validate обратно в this.props, чтобы добиться уровня абстракции/расширяемости.
Кроме того, я последовал совету Пагоаги и использовал className вместо class (у меня пока нет такой мышечной памяти).
Хорошая работа, всегда приятно видеть продолжение, когда люди решают свои проблемы.
Появляется ошибка (спасибо за исправление!), но теперь проблема остается: состояние
errorустарело после первоначального рендеринга.