Реагировать на устаревшую дату ошибки для дочерних компонентов

Я пытаюсь, чтобы элементы ввода формы, которые не контролируются из-за нашего использования 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 на самом деле не обновлял состояние.

Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
В настоящее время производительность загрузки веб-сайта имеет решающее значение не только для удобства пользователей, но и для ранжирования в...
Безумие обратных вызовов в javascript [JS]
Безумие обратных вызовов в javascript [JS]
Здравствуйте! Юный падаван 🚀. Присоединяйся ко мне, чтобы разобраться в одной из самых запутанных концепций, когда вы начинаете изучать мир...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
6
0
251
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

У вас есть атрибут «класс» внутри нескольких ваших функций рендеринга, замена его на «имя класса» позволит отобразить ошибку: https://codepen.io/BPagoaga/pen/QoMXmw

return <div className = "col-md-2">

Появляется ошибка (спасибо за исправление!), но теперь проблема остается: состояние error устарело после первоначального рендеринга.

Mike Warren 11.03.2019 16:52
Ответ принят как подходящий

Я исправил проблему!

Что я сделал

Вместо того, чтобы сохранять 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 (у меня пока нет такой мышечной памяти).

Хорошая работа, всегда приятно видеть продолжение, когда люди решают свои проблемы.

Jacob 12.03.2019 16:45

Другие вопросы по теме