Реагировать на потерю состояния useContext

У меня проблема, которую я пытался решить последние пару дней:

Я использую поставщик контекста для полей формы, и по какой-то причине поля продолжают перезаписывать друг друга, когда я использую memo.

Провайдер:

export const Context = React.createContext();

function Form_Provider({ values, onChange, children }) {
  return (
    <Context.Provider value={{ values, onChange }}>
      {children}
    </Context.Provider>
  )
});
export default Form_Provider

Поле:

function Field({ label, name }) {
  const { values, onChange } = useContext(Context);
  return (
      <Memorable
        label={label}
        onChange={({ target: t }) => onChange({ name, value: t.value })}
        value={values[name]}
      />
  );


}

const Memorable = React.memo(props => {
  return (
      <Form.Item label={props.label}>
        <Input
          value={props.value}
          onChange={props.onChange}
        />
      </Form.Item>
    </>
  )
}, ({ value: newValue}, { value: oldValue }) => newValue == oldValue)

Форма

const [formValues, setFormValues] = useState({ field1: 'Foo', field2: 'Bar' });

<Form.Provider
  values={formValues}
  onChange={({ name, value }) => setFormValues({...formValues, [name]: value }))
>
  <Form.Field name='field1' label="Field 1" />
  <Form.Field name='field2' label="Field 2" />
</Form.Provider>

(Пытался максимально упростить)

В моем фактическом коде я добавил prettifier печати json для отслеживания состояния для каждого поля, и он работает до тех пор, пока каждое поле не будет, когда я редактирую только одно поле. Однако, как только я начинаю редактировать другое поле, первое поле, которое я редактировал, возвращается в исходное состояние и / или в какое-то другое странное состояние между состоянием из прошлого.

Если я не использую Memo, он работает, но это не может быть решением, так как я буду работать с большим количеством полей, и это приведет к частому повторному рендерингу.

Кто-нибудь знает, что здесь происходит?

Добавление:

Я уже пробовал использовать для этого внутренний редуктор и передавать функцию диспетчеризации. Пока я не пытаюсь управлять состоянием вне провайдера, все работает.


person Matt Brandt    schedule 04.12.2020    source источник


Ответы (1)


Я почти уверен, что проблема в том, что вы запоминаете исходные значения formValues отсюда:

onChange={({ name, value }) => setFormValues({...formValues, [name]: value }))

Скажем, у field1 изменилось значение. Он вызывает обработчик onChange, который объединяет ...formValues - значения, существовавшие при установке компонента, - с новым значением для field1.

Теперь функция равенства в React.memo для field1 возвращает false, потому что значение другое. Это конкретное поле перерисовывается, чтобы получить новое значение, а также новые значения ...formValues. Однако другие поля не повторно отрисованы. Для них ...formValues по-прежнему означает значение состояния, которое существовало при последней повторной визуализации, то есть при монтировании компонента.

Если вы теперь измените значение field2, состояние будет установлено в результате слияния исходного состояния с новым значением field2. Следовательно, field1 сбрасывается, потому что его значение теперь снова изменилось на исходное значение.

Простым решением этой проблемы было бы использование версии обратного вызова setState, которая всегда использует текущее значение состояния:

onChange={({ name, value }) => setFormValues(fv => {...fv, [name]: value }))

Однако у меня возникло бы искушение не делать этого, а вместо этого вообще избавиться от воспоминаний. Это связано с тем, что ваша функция равенства на самом деле неточно отражает способ изменения свойств, предоставленных компоненту. Я считаю, что прирост производительности здесь также незначителен, потому что компонент настолько мал и сам не отображает никаких дополнительных компонентов.

Предполагая, что анимация не связана с изменением значения, ее выполнение очень дешево и не является хорошим кандидатом для запоминания, что также позволяет избежать встроенной оптимизации React. Вы должны хорошо подумать, чтобы решить, действительно ли он вам нужен, прежде чем внедрять его.

person lawrence-witt    schedule 04.12.2020
comment
Очень признателен за ваш вклад / помощь! Это работает! Я провел последние две ночи над этим, почти не сплю! Причина, по которой я реализовал React.memo, на самом деле заключается в том, что я вижу проблемы с рендерингом / производительностью. В моей форме (-ах) более 30 полей, разбросанных по разным вкладкам, и нажатие на клавиатуру вызывает видимую задержку. - person Matt Brandt; 05.12.2020
comment
Достаточно справедливо - если вы видите разницу, то, вероятно, это стоит сделать. - person lawrence-witt; 05.12.2020