react-hook-form与react-select如何将值保存为值数组而不是选项对象数组。

huangapple go评论77阅读模式
英文:

react-hook-form with react-select how to save values as an array of values instead of array of option objects

问题

我正在使用 chakra-react-select,基本上只是 react-select 的一个经过样式定制的 Chakra 兼容版本。

我也在使用 react-hook-form,我已经通过编写以下代码使该组件与 React 表单很好地配合使用:

<Controller
  control={control}
  name={name}
  rules={validation}
  render={({ field }) => {
    const Component = isCreatable ? CreatableSelect : Select;

    return (
      <Component
        isRequired={Boolean(validation?.required)}
        isClearable={isClearable}
        isDisabled={isDisabled}
        isLoading={isLoading}
        isMulti={isMulti}
        isSearchable={isSearchable}
        options={options}
        {...field}
      />
    );
  }}
/>

这个方法有效,但我的唯一问题是保存到表单的值不是值数组(这是我的预期)。相反,我得到一个选定选项的数组(即整个选项对象)。

我期望的是:['value1', 'value2', ...]

而我得到的是:[{ label: 'Value 1', value: 'value1' }, ...]

在表单上保存数据的最简洁方法是什么?我无法在提交时进行更改,因为我的 yup 验证会在表单提交事件完成之前应用验证规则。

英文:

I'm using chakra-react-select which is basically just a styled Chakra compatible version of react-select.

I'm also using react-hook-form, I've hooked up the component to work nicely with react form by writing the following bit of code:

<Controller
  control={control}
  name={name}
  rules={validation}
  render={({ field }) => {
    const Component = isCreatable ? CreatableSelect : Select;

    return (
      <Component
        isRequired={Boolean(validation?.required)}
        isClearable={isClearable}
        isDisabled={isDisabled}
        isLoading={isLoading}
        isMulti={isMulti}
        isSearchable={isSearchable}
        options={options}
        {...field}
      />
    );
  }}
/>

This works but my only problem is the value that gets saved to the form is not an array of values (which is what I expect). Instead I get an array of selected options (i.e. the entire option object).

I expect: ['value1', 'value2', ...]

Instead I get [{ label: 'Value 1', value: 'value1' }, ...]

What is the neatest way to change they way the data is saved on my form? I can't do it on the submit because my yup validations apply the validation rules before the form submit event finished.

答案1

得分: 0

以下是翻译好的内容:

这个问题的解决方法是覆盖field中的onChangevalue键,像这样:

<Controller
  control={control}
  name={name}
  rules={validation}
  render={({ field: { onChange, value, ...field } }) => {
    const Component = isCreatable ? CreatableSelect : Select;

    function handleChange(newValue) {
      onChange(getChangedValue(newValue));
    }

    const internalValue = getInitialValue(value);

    return (
      <Component
        isRequired={Boolean(validation?.required)}
        isClearable={isClearable}
        isDisabled={isDisabled}
        isLoading={isLoading}
        isMulti={isMulti}
        isSearchable={isSearchable}
        options={options}
        onChange={handleChange}
        value={internalValue}
        {...field}
      />
    );
  }}
/>

简而言之,需要在onChange期间将值转换为字符串数组,然后从字符串数组和options派生internalValue

完整的解决方案,适用于多选和可创建的选择,如下所示:

首先,在组件的顶部添加以下内容:

const [allOptions, setAllOptions] = useState(options);
// `options`是传递给SelectComponent的选项属性

然后按照以下方式更新您的Controller:

<Controller
  control={control}
  name={name}
  rules={validation}
  render={({ field: { onChange, value, ...field } }) => {
    const Component = isCreatable ? CreatableSelect : Select;

    function handleChange(newValue: SingleValue | MultiValue) {
      handleNewOption({ isCreatable, isMulti, newValue, setAllOptions });
      onChange(getChangedValue({ isMulti, newValue }));
    }

    const internalValue = getInternalValue({ allOptions, isMulti, value });

    return (
      <Component
        isRequired={Boolean(validation?.required)}
        isClearable={isClearable}
        isDisabled={isDisabled}
        isLoading={isLoading}
        isMulti={isMulti}
        isSearchable={isSearchable}
        options={options}
        onChange={handleChange}
        value={internalValue}
        {...field}
      />
    );
  }}
/>

然后,以下是辅助函数:

interface GetChangeValueArgs {
  isMulti: boolean;
  newValue: SingleValue | MultiValue;
}

export function getChangedValue(
{ isMulti, newValue }: GetChangeValueArgs
): MultiValue | SingleValue {
  if (!newValue) {
    return null;
  }
  if (isMulti) {
    return (newValue as MultiValue).map(
      (v) => v.value
    ) as MultiValue;
  }
  return newValue.value as SingleValue;
}

interface GetInternalValueArgs<T> {
  allOptions: FormSelectOption<T>[];
  isMulti: boolean;
  value: T | T[];
}
export function getInternalValue(
  { allOptions, isMulti, value }: GetInternalValueArgs<T>
) {
  if (isMulti) {
    if (!value) {
      return null;
    }
    return (value as T[])
      .map((optionValue) =>
        allOptions.find((option) => option.value === optionValue)
      )
      .reduce((valueItems, valueItem) => {
        if (!valueItem) {
          return valueItems;
        }

        return [...valueItems, valueItem];
      }, []);
  }
  return allOptions.find((option) => option.value === value) || null;
}

interface HandleNewOptionArgs<T> {
  isCreatable: boolean;
  isMulti: boolean;
  newValue: SingleValue<FormSelectOption<T>> | MultiValue<FormSelectOption<T>>;
  setAllOptions: Dispatch<SetStateAction<FormSelectOption<T>[]>>;
}

export function handleNewOption({
  isMulti,
  isCreatable,
  newValue,
  setAllOptions,
}: HandleNewOptionArgs) {
  if (isCreatable && newValue) {
    if (isMulti) {
      newValue.forEach((newOption) => {
        if ('__isNew__' in newOption && newOption.__isNew__) {
          setAllOptions((prevOptions) => [...prevOptions, newOption]);
        }
      });
    } else {
      if (
        newValue &&
        '__isNew__' in newValue &&
        newValue.__isNew__
      ) {
        setAllOptions((prevOptions) => [...prevOptions, newValue]);
      }
    }
  }
}
英文:

The solution to this is to overwrite the onChange and value keys from field like this:

&lt;Controller
control={control}
name={name}
rules={validation}
render={({ field: { onChange, value, ...field } }) =&gt; {
const Component = isCreatable ? CreatableSelect : Select;
function handleChange(newValue) {
onChange(getChangedValue(newValue);
}
const internalValue = getInitialValue(value)
return (
&lt;Component
isRequired={Boolean(validation?.required)}
isClearable={isClearable}
isDisabled={isDisabled}
isLoading={isLoading}
isMulti={isMulti}
isSearchable={isSearchable}
options={options}
onChange={handleChange}
value={internalValue}
{...field}
/&gt;
);
}}
/&gt;

That is the simple just of it, the values need to be transformed to the string array during onChange but then the internalValue needs to be derived from the string array and options.


The full solution which caters for Multiple Selects and Creatable Selects is as follows:

First add this to the top of the component:

const [allOptions, setAllOptions] = useState(options);
// `options` is the options prop that you pass to the SelectComponent

Then update your Controller as follows:

&lt;Controller
control={control}
name={name}
rules={validation}
render={({ field: { onChange, value, ...field } }) =&gt; {
const Component = isCreatable ? CreatableSelect : Select;
function handleChange(newValue: SingleValue | MultiValue) {
handleNewOption({ isCreatable, isMulti, newValue, setAllOptions });
onChange(getChangedValue({ isMulti, newValue }));
}
const internalValue = getInternalValue({ allOptions, isMulti, value });
return (
&lt;Component
isRequired={Boolean(validation?.required)}
isClearable={isClearable}
isDisabled={isDisabled}
isLoading={isLoading}
isMulti={isMulti}
isSearchable={isSearchable}
options={options}
onChange={handleChange}
value={internalValue}
{...field}
/&gt;
);
}}
/&gt;

And then here are the helper functions:

interface GetChangeValueArgs {
isMulti: boolean;
newValue: SingleValue | MultiValue;
}
export function getChangedValue(
{ isMulti, newValue }: GetChangeValueArgs
): MultiValue | SingleValue {
if (!newValue) {
return null;
}
if (isMulti) {
return (newValue as MultiValue).map(
(v) =&gt; v.value
) as MultiValue;
}
return newValue.value as SingleValue;
}
interface GetInternalValueArgs&lt;T&gt; {
allOptions: FormSelectOption&lt;T&gt;[];
isMulti: boolean;
value: T | T[];
}
export function getInternalValue(
{ allOptions, isMulti, value }: GetInternalValueArgs&lt;T&gt;
) {
if (isMulti) {
if (!value) {
return null;
}
return (value as T[])
.map((optionValue) =&gt;
allOptions.find((option) =&gt; option.value === optionValue)
)
.reduce((valueItems, valueItem) =&gt; {
if (!valueItem) {
return valueItems;
}
return [...valueItems, valueItem];
}, []);
}
return allOptions.find((option) =&gt; option.value === value) || null;
}
interface HandleNewOptionArgs&lt;T&gt; {
isCreatable: boolean;
isMulti: boolean;
newValue: SingleValue&lt;FormSelectOption&lt;T&gt;&gt; | MultiValue&lt;FormSelectOption&lt;T&gt;&gt;;
setAllOptions: Dispatch&lt;SetStateAction&lt;FormSelectOption&lt;T&gt;[]&gt;&gt;;
}
export function handleNewOption({
isMulti,
isCreatable,
newValue,
setAllOptions,
}: HandleNewOptionArgs) {
if (isCreatable &amp;&amp; newValue) {
if (isMulti) {
newValue.forEach((newOption) =&gt; {
if (&#39;__isNew__&#39; in newOption &amp;&amp; newOption.__isNew__) {
setAllOptions((prevOptions) =&gt; [...prevOptions, newOption]);
}
});
} else {
if (
newValue &amp;&amp;
&#39;__isNew__&#39; in newValue &amp;&amp;
newValue.__isNew__
) {
setAllOptions((prevOptions) =&gt; [...prevOptions, newValue]);
}
}
}
}

huangapple
  • 本文由 发表于 2023年6月1日 23:28:43
  • 转载请务必保留本文链接:https://go.coder-hub.com/76383509.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定