英文:
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
中的onChange
和value
键,像这样:
<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:
<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}
/>
);
}}
/>
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:
<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}
/>
);
}}
/>
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) => 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]);
}
}
}
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论