Обновление состояния при смене реквизита в React Form

104

У меня возникают проблемы с формой React и правилом управления государством. У меня есть поле ввода времени в форме (в модальном). Начальное значение устанавливается как переменная состояния в getInitialState и передается из родительского компонента. Это само по себе прекрасно работает.

Проблема возникает, когда я хочу обновить значение start_time по умолчанию через родительский компонент. Само обновление происходит в родительском компоненте через setState start_time: new_time. Однако в моей форме значение start_time по умолчанию никогда не изменяется, поскольку оно определяется только один раз в getInitialState.

Я попытался использовать componentWillUpdate для принудительного изменения состояния через setState start_time: next_props.start_time, который действительно работал, но дал мне ошибки Uncaught RangeError: Maximum call stack size exceeded.

Итак, мой вопрос: какой правильный способ обновления состояния в этом случае? Думаю ли я об этом неправильно?

Текущий код:

@ModalBody = React.createClass
  getInitialState: ->
    start_time: @props.start_time.format("HH:mm")

  #works but takes long and causes:
  #"Uncaught RangeError: Maximum call stack size exceeded"
  componentWillUpdate: (next_props, next_state) ->
    @setState(start_time: next_props.start_time.format("HH:mm"))

  fieldChanged: (fieldName, event) ->
    stateUpdate = {}
    stateUpdate[fieldName] = event.target.value
    @setState(stateUpdate)

  render: ->
    React.DOM.div
      className: "modal-body"
      React.DOM.form null,
        React.createElement FormLabelInputField,
          type: "time"
          id: "start_time"
          label_name: "Start Time"
          value: @state.start_time
          onChange: @fieldChanged.bind(null, "start_time")

@FormLabelInputField = React.createClass
  render: ->
    React.DOM.div
      className: "form-group"
      React.DOM.label
        htmlFor: @props.id
        @props.label_name + ": "
      React.DOM.input
        className: "form-control"
        type: @props.type
        id: @props.id
        value: @props.value
        onChange: @props.onChange
Теги:

7 ответов

233
Лучший ответ

Если я правильно понимаю, у вас есть родительский компонент, который передает start_time до компонента ModalBody, который присваивает его своему собственному состоянию? И вы хотите обновить это время от родителя, а не от дочернего компонента.

У React есть несколько советов по работе с этим сценарием. (Обратите внимание, что это старая статья, которая с тех пор была удалена из Интернета. ссылка на текущий doc на компонентные реквизиты).

Использование реквизита для генерации состояния в getInitialState часто приводит к дублированию "источника истины", то есть к реальным данным. Это связано с тем, что getInitialState вызывается только при первом создании компонента.

По возможности, вычисляйте значения "на лету", чтобы они не выходили из синхронизации позже и вызывают проблемы с обслуживанием.

В принципе, всякий раз, когда вы назначаете родительский props дочернему элементу state, метод рендеринга не всегда вызывается при обновлении prop. Вы должны вызвать его вручную, используя метод componentWillReceiveProps.

componentWillReceiveProps(nextProps) {
  // You don't have to do this check first, but it can help prevent an unneeded render
  if (nextProps.startTime !== this.state.startTime) {
    this.setState({ startTime: nextProps.startTime });
  }
}
  • 1
    Благодарю. это мне очень помогает
  • 0
    Что делать, если вы хотите сделать «И вы хотите обновить это время от родительского, а не дочернего компонента». наоборот?
Показать ещё 10 комментариев
36

Видимо, все меняется.... getDerivedStateFromProps() теперь является предпочтительной функцией.

class Component extends React.Component {
  static getDerivedStateFromProps(props, current_state) {
    if (current_state.value !== props.value) {
      return {
        value: props.value,
        computed_prop: heavy_computation(props.value)
      }
    }
    return null
  }
}

(приведенный выше код от danburzo @github)

  • 4
    К вашему сведению, вы должны вернуть также null если ничего не должно измениться сразу после вашего if, вы должны пойти с return null
  • 0
    @ IlgıtYıldırım - отредактировал код, так как 4 человека проголосовали за ваш комментарий - действительно ли это имеет значение?
Показать ещё 1 комментарий
9

Также есть компонент componentDidUpdate.

Функция подписи:

componentDidUpdate(prevProps, prevState, snapshot)

Используйте это как возможность работать на DOM, когда компонент был обновлен. Не вызывается при начальном render.

Увидимся, вероятно, вам не нужна статья о производном состоянии, которая описывает Anti-Pattern как для componentDidUpdate и для getDerivedStateFromProps. Я нахожу это очень полезным.

3

componentWillReceiveProps устарела, потому что его использование "часто приводит к ошибкам и несоответствиям".

Если что-то меняется извне, рассмотрите возможность полного сброса дочернего компонента с помощью key.

Предоставление key подпорки дочернему компоненту гарантирует, что всякий раз, когда значение key изменяется извне, этот компонент перерисовывается. Например,

<EmailInput
  defaultEmail={this.props.user.email}
  key={this.props.user.id}
/>

По его производительности:

Хотя это может звучать медленно, разница в производительности, как правило, незначительна. Использование ключа может быть даже быстрее, если компоненты имеют сложную логику, которая запускается при обновлении, поскольку diffing обходится для этого поддерева.

  • 2
    это блестяще и отлично работает в React 16
  • 1
    Ключ, секрет! Прекрасно работает в React 16, как указано выше
Показать ещё 1 комментарий
1

Из реагирующей документации: https://reactjs.org/blog/2018/06/07/you-probbly-dont-need-derived-state.html

Стирание состояния при смене реквизита - это анти-паттерн

Начиная с версии 16, componentWillReceiveProps устарела. Из реактивной документации, рекомендуемый подход в этом случае является использование

  1. Полностью контролируемый компонент: ParentComponent из ModalBody будет владеть состоянием start_time. Это не мой предпочтительный подход в этом случае, так как я думаю, что модал должен владеть этим состоянием.
  2. Полностью неконтролируемый компонент с ключом: это мой предпочтительный подход. Пример из реагирующей документации: https://codesandbox.io/s/6v1znlxyxn. Вы бы полностью владели состоянием start_time из вашего ModalBody и использовали бы getInitialState как вы уже сделали. Чтобы сбросить состояние start_time, вы просто меняете ключ из ParentComponent
  • 0
    Добавление ключа помогло!
0

Возможно, вам не нужно производное состояние

1. Установить ключ от родителя

При изменении ключа React создаст новый экземпляр компонента, а не обновит текущий. Ключи обычно используются для динамических списков, но также полезны здесь.

2. Используйте getDerivedStateFromProps/componentWillReceiveProps

Если по какой-то причине ключ не работает (возможно, компонент очень дорог для инициализации)

Используя getDerivedStateFromProps вы можете сбросить любую часть состояния, но в настоящее время она выглядит немного глючной (v16.7) !, см. Ссылку выше для использования.

0

Это вполне понятно из их документов:

If you used componentWillReceiveProps for re-computing some data only when a prop changes, use a memoization helper instead.

Используйте: https://reactjs.org/blog/2018/06/07/you-probbly-dont-need-derived-state.html#what-about-memoization

Ещё вопросы

Сообщество Overcoder
Наверх
Меню