Условные выпадающие списки с response-select в response-final-form инициализируются из состояния

Я использую react-select и react-final-form для условных раскрывающихся списков, где параметры для второго выбора предоставляются компонентом <PickOptions/> на основе значения первого выбора (благодаря этому SO answer).

Вот компонент:

/** Changes options and clears field B when field A changes */
const PickOptions = ({ a, b, optionsMap, children }) => {
  const aField = useField(a, { subscription: { value: 1 } });
  const bField = useField(b, { subscription: {} });
  const aValue = aField.input.value.value;
  const changeB = bField.input.onChange;
  const [options, setOptions] = React.useState(optionsMap[aValue]);

  React.useEffect(() => {
    changeB(undefined); // clear B
    setOptions(optionsMap[aValue]);
  }, [aValue, changeB, optionsMap]);
  return children(options || []);
};

Он очищает второй выбор, когда значение первого изменяется на changeB(undefined). Я также установил второй выбор для первой опции в массиве, передав initialValue. Поскольку мне нужно инициализировать значения из состояния, я получил следующий код:

initialValue={
  this.state.data.options[index] &&
  this.state.data.options[index].secondOption
    ? this.state.data.options[index]
        .secondOption
    : options.filter(
        option => option.type === "option"
      )[0]
}

Но это не работает. Начальные значения из состояния не передаются в поля, отображаемые <PickOptions/>. Если я удаляю changeB(undefined) из компонента, значения передаются, но тогда входное значение второго выбора не обновляется, когда значение первого выбора изменяется (даже если параметры были обновлены). Вот ссылка на мой codeandbox.

Как я могу это исправить?


person jupiteror    schedule 08.11.2019    source источник


Ответы (2)


Мне удалось заставить это работать, взяв все, что отображается в разделе fields.map(), и обернув его в собственный компонент, чтобы гарантировать, что каждый из них имеет отдельные состояния. Затем я просто помещаю функцию changeB(undefined) в обратный вызов хука useEffect, чтобы очистить вторичные выборки после того, как пользователь выберет другую опцию для первого выбора, например:

React.useEffect(() => {
  setOptions(optionsMap[aValue]);

  return function cleanup() {
    changeB(undefined) // clear B
  };
}, [aValue, changeB, optionsMap]);

Вы можете увидеть, как это работает в этой песочнице: React Final Form - Отменить выбор второстепенных.

Чтобы изменить вторичные поля выбора, вам нужно будет передать дополнительное свойство PickOptions для типа параметра, которому соответствует массив. Я также подписываюсь и отслеживаю предыдущий bValue, чтобы проверить, существует ли он в текущем массиве bValueSet. Если он существует, мы оставляем его в покое, в противном случае мы обновляем его первым значением в соответствующем массиве optionType.

  // subscibe to keep track of previous bValue
  const bFieldSubscription = useField(b, { subscription: { value: 1 } })
  const bValue = bFieldSubscription.input.value.value

  React.useEffect(() => {
    setOptions(optionsMap[aValue]);

    if (optionsMap[aValue]) {
      // set of bValues defined in array
      const bValueSet = optionsMap[aValue].filter(x => x.type === optionType);

      // if the previous bValue does not exist in the current bValueSet then changeB
      if (!bValueSet.some(x => x.value === bValue)) {
        changeB(bValueSet[0]); // change B
      }
    }

  }, [aValue, changeB, optionsMap]);

Вот песочница для этого метода: React Final Form - Update Secondary Выбирает.

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

person F. Serna    schedule 15.11.2019
comment
Большое спасибо за Ваш ответ! Я это попробую. Но мне также нужно, чтобы второй выбор был установлен на первый параметр в массиве после изменения первого выбора. Это должно происходить только тогда, когда поле изменяется вручную пользователем, а не когда форма инициализируется из состояния. См. Код для initialValue в моем примере выше. - person jupiteror; 15.11.2019
comment
@jupiteror Я изменил свой ответ, чтобы понять, какой, на мой взгляд, желаемый результат. Проверьте вторую ссылку на песочницу. - person F. Serna; 16.11.2019
comment
Спасибо! Но значения во втором примере теперь не инициализированы (это проблема, которую я пытаюсь решить. - person jupiteror; 16.11.2019
comment
@jupiteror Мне очень жаль, что я, должно быть, не сохранил его правильно, попробуйте второй пример еще раз, значения должны быть инициализированы и измениться при перемещении первого параметра. - person F. Serna; 16.11.2019
comment
Теперь есть еще две проблемы. Во-первых, приложение вылетает при попытке добавить новую опцию. Во-вторых, значение второй опции всегда устанавливается равным первой опции в массиве, а не той, которая указана в состоянии. Второй вариант во втором поле должен быть Two B, но это Two A. - person jupiteror; 16.11.2019
comment
Как это должно работать: поле B имеет значение в состоянии, когда форма инициализирована → применяется значение из состояния. Не имеет → применяется первая опция в массиве. Пользователь изменяет значение поля A → применяется первая опция в массиве. - person jupiteror; 16.11.2019
comment
Хорошо, поэтому я исправил ошибку, из-за которой приложение вылетало при добавлении новой опции, и исправил ее, чтобы оно загружало начальное состояние, посмотрите на обновленный компонент PickOptions. Итак, я вижу, где я неправильно понимал раньше, и хотел бы получить больше отзывов, если я все еще не вижу проблему правильно @jupiteror. - person F. Serna; 16.11.2019
comment
Мы почти у цели ???? Но есть еще одна проблема. Когда вы добавляете новую опцию и меняете поле A, приложение все равно вылетает. - person jupiteror; 16.11.2019
comment
@jupiteror Мне не удалось воспроизвести вашу проблему, пока я не открыл песочницу в окне инкогнито, так что, вероятно, это снова была моя ошибка в том, что я не сохранил файлы правильно. Я обновил его и, надеюсь, теперь все в порядке. - person F. Serna; 16.11.2019
comment
Теперь все работает, спасибо большое! Я постараюсь перенести это в свой проект, и если у меня возникнут проблемы, я дам вам знать. Я очень ценю вашу помощь! ???? - person jupiteror; 16.11.2019
comment
Основываясь на вашем ответе, я получил немного другой код. Пожалуйста, посмотрите мой ответ ниже. Как и в моем проекте, значения параметров в разных наборах могут быть одинаковыми. Так что лучше полагаться на факт, обновлялся ли aFiled пользователем или нет. - person jupiteror; 17.11.2019

Основываясь на предыдущем ответе, я получил следующий код в моем компоненте:

// subscibe to keep track of aField has been changed
const aFieldSubscription = useField(a, { subscription: { dirty: 1 } });

React.useEffect(() => {
  setOptions(optionsMap[aValue]);

  if (optionsMap[aValue]) {
    // set of bValues defined in array
    const bValueSet = optionsMap[aValue].filter(x => x.type === optionType);

    if (aFieldSubscription.meta.dirty) {
      changeB(bValueSet[0]); // change B
    }
  }
}, [aValue, changeB, optionsMap]);

Таким образом, он проверяет, было ли изменено aField пользователем, и, если это правда, устанавливает значение bField на первую опцию в массиве.

person jupiteror    schedule 17.11.2019