Material UI + React Form Hook + 多个复选框 + 默认选中
Material UI + React Form Hook + multiple checkboxes + default selected
我正在尝试使用 react-form-hook
Material UI
.
构建一个可容纳多个 'grouped' 复选框的表单
复选框是从 HTTP 请求异步创建的。
我想提供一组对象 ID 作为默认值:
defaultValues: { boat_ids: trip?.boats.map(boat => boat.id.toString()) || [] }
此外,当我 select 或 deselect 复选框时,我想 add/remove 对象的 ID 到值react-hook-form
.
即。 (boat_ids: [25, 29, 4]
)
我怎样才能做到这一点?
这是我正在尝试重现该问题的 sample。
加分,使用 Yup
验证最小 selected 复选框
boat_ids: Yup.array() .min(2, "")
API 在 6.X 中所做的重大更改:
- 验证选项已更改为使用解析器函数包装器和不同的配置属性名称
注意:文档只是针对 validationResolver->resolver 进行了修复,repo 中用于验证的代码示例尚未更新(仍然使用 validationSchema
进行测试)。感觉好像他们不确定他们想用那里的代码做什么,而且它处于一种不确定的状态。我会完全避开他们的 Controller 直到它安定下来,或者使用 Controller 作为你自己的表单 Controller HOC 的薄包装,这似乎是他们想要进入的方向。
请参阅 official sandbox demo 和 "false"
值的意外行为作为复选框的字符串以供参考
import { yupResolver } from "@hookform/resolvers";
const { register, handleSubmit, control, getValues, setValue } = useForm({
resolver: yupResolver(schema),
defaultValues: Object.fromEntries(
boats.map((boat, i) => [
`boat_ids[${i}]`,
preselectedBoats.some(p => p.id === boats[i].id)
])
)
});
Controller
不再原生处理 Checkbox (type="checkbox"
),或者更确切地说,错误地处理值。它不检测复选框的布尔值,并尝试将其转换为字符串值。您有几个选择:
- 不要使用
Controller
。使用不受控制的输入
- 使用新的
render
道具为您的复选框使用自定义渲染函数并添加一个 setValue 挂钩
- 像表单控制器 HOC 一样使用 Controller 并手动控制所有输入
避免使用控制器的例子:
https://codesandbox.io/s/optimistic-paper-h39lq
https://codesandbox.io/s/silent-mountain-wdiov
与第一个原始示例相同,但使用 yupResolver
wrapper
5.X 的说明:
这是一个不需要控制器的简化示例。不受控制是文档中的建议。仍然建议您在数据上为每个输入提供自己的 name
和 transform/filter 以删除未经检查的值,例如在后一个示例中使用 yup 和 validatorSchema,但为了示例的目的,使用相同的名称会导致将值添加到符合您要求的数组中。
https://codesandbox.io/s/practical-dijkstra-f1yox
无论如何,问题是您的 defaultValues
与复选框的结构不匹配。它应该是 {[name]: boolean}
,其中生成的 names
是 文字字符串 boat_ids[${boat.id}]
,直到它通过不受控制的表单输入,这些输入将值组合在一起成一个阵列。例如:form_input1[0] form_input1[1]
发出 form_input1 == [value1, value2]
https://codesandbox.io/s/determined-paper-qb0lf
建造 defaultValues: { "boat_ids[0]": false, "boat_ids[1]": true ... }
控制器需要布尔值来切换复选框值,并将其作为默认值提供给复选框。
const { register, handleSubmit, control, getValues, setValue } = useForm({
validationSchema: schema,
defaultValues: Object.fromEntries(
preselectedBoats.map(boat => [`boat_ids[${boat.id}]`, true])
)
});
用于 validationSchema 的模式,验证至少选择了 2 个,并在将数据发送到 onSubmit 之前将数据转换为所需的模式。它过滤掉错误的值,所以你得到一个字符串 id 数组:
const schema = Yup.object().shape({
boat_ids: Yup.array()
.transform(function(o, obj) {
return Object.keys(obj).filter(k => obj[k]);
})
.min(2, "")
});
这是一个工作版本:
import React from "react";
import { useForm, Controller } from "react-hook-form";
import FormControlLabel from "@material-ui/core/FormControlLabel";
import Checkbox from "@material-ui/core/Checkbox";
export default function CheckboxesGroup() {
const { control, handleSubmit } = useForm({
defaultValues: {
bill: "bill",
luo: ""
}
});
return (
<form onSubmit={handleSubmit(e => console.log(e))}>
{["bill", "luo"].map(name => (
<Controller
key={name}
name={name}
as={
<FormControlLabel
control={<Checkbox value={name} />}
label={name}
/>
}
valueName="checked"
type="checkbox"
onChange={([e]) => {
return e.target.checked ? e.target.value : "";
}}
control={control}
/>
))}
<button>Submit</button>
</form>
);
}
codesandbox link: https://codesandbox.io/s/material-demo-65rjy?file=/demo.js:0-932
但是,我不建议这样做,因为 material UI 中的 Checkbox 可能应该 return 检查(布尔值)而不是(值)。
我也一直在为此苦苦挣扎,这对我有用。
更新了 react-hook-form v6 的解决方案,它也可以在没有 useState
(下面的沙盒 link)的情况下完成:
import React, { useState } from "react";
import { useForm, Controller } from "react-hook-form";
import FormControlLabel from "@material-ui/core/FormControlLabel";
import Checkbox from "@material-ui/core/Checkbox";
export default function CheckboxesGroup() {
const defaultNames = ["bill", "Manos"];
const { control, handleSubmit } = useForm({
defaultValues: { names: defaultNames }
});
const [checkedValues, setCheckedValues] = useState(defaultNames);
function handleSelect(checkedName) {
const newNames = checkedValues?.includes(checkedName)
? checkedValues?.filter(name => name !== checkedName)
: [...(checkedValues ?? []), checkedName];
setCheckedValues(newNames);
return newNames;
}
return (
<form onSubmit={handleSubmit(data => console.log(data))}>
{["bill", "luo", "Manos", "user120242"].map(name => (
<FormControlLabel
control={
<Controller
name="names"
render={({ onChange: onCheckChange }) => {
return (
<Checkbox
checked={checkedValues.includes(name)}
onChange={() => onCheckChange(handleSelect(name))}
/>
);
}}
control={control}
/>
}
key={name}
label={name}
/>
))}
<button>Submit</button>
</form>
);
}
Codesandbox link: https://codesandbox.io/s/material-demo-54nvi?file=/demo.js
没有useState
完成默认选择项目的另一种解决方案:
https://codesandbox.io/s/material-demo-bzj4i?file=/demo.js
这是我的解决方案,它没有使用 Material UI 中的所有默认组件,因为在我的界面上每个收音机都会有一个图标和文本,除了不显示默认项目符号点:
const COMPANY = "company";
const INDIVIDUAL = "individual";
const [scope, setScope] = useState(context.scope || COMPANY);
const handleChange = (event) => {
event.preventDefault();
setScope(event.target.value);
};
<Controller
as={
<FormControl component="fieldset">
<RadioGroup
aria-label="scope"
name="scope"
value={scope}
onChange={handleChange}
>
<FormLabel>
{/* Icon from MUI */}
<Business />
<Radio value={COMPANY} />
<Typography variant="body1">Company</Typography>
</FormLabel>
<FormLabel>
{/* Icon from MUI */}
<Personal />
<Radio value={INDIVIDUAL} />
<Typography variant="body1">Individual</Typography>
</FormLabel>
</RadioGroup>
</FormControl>
}
name="scope"
control={methods.control}
/>;
观察:在这个例子中,我使用没有破坏的React Hook Form:
const methods = useForm({...})
我正在尝试使用 react-form-hook
Material UI
.
复选框是从 HTTP 请求异步创建的。
我想提供一组对象 ID 作为默认值:
defaultValues: { boat_ids: trip?.boats.map(boat => boat.id.toString()) || [] }
此外,当我 select 或 deselect 复选框时,我想 add/remove 对象的 ID 到值react-hook-form
.
即。 (boat_ids: [25, 29, 4]
)
我怎样才能做到这一点?
这是我正在尝试重现该问题的 sample。
加分,使用 Yup
验证最小 selected 复选框boat_ids: Yup.array() .min(2, "")
API 在 6.X 中所做的重大更改:
- 验证选项已更改为使用解析器函数包装器和不同的配置属性名称
注意:文档只是针对 validationResolver->resolver 进行了修复,repo 中用于验证的代码示例尚未更新(仍然使用validationSchema
进行测试)。感觉好像他们不确定他们想用那里的代码做什么,而且它处于一种不确定的状态。我会完全避开他们的 Controller 直到它安定下来,或者使用 Controller 作为你自己的表单 Controller HOC 的薄包装,这似乎是他们想要进入的方向。
请参阅 official sandbox demo 和"false"
值的意外行为作为复选框的字符串以供参考
import { yupResolver } from "@hookform/resolvers";
const { register, handleSubmit, control, getValues, setValue } = useForm({
resolver: yupResolver(schema),
defaultValues: Object.fromEntries(
boats.map((boat, i) => [
`boat_ids[${i}]`,
preselectedBoats.some(p => p.id === boats[i].id)
])
)
});
Controller
不再原生处理 Checkbox (type="checkbox"
),或者更确切地说,错误地处理值。它不检测复选框的布尔值,并尝试将其转换为字符串值。您有几个选择:
- 不要使用
Controller
。使用不受控制的输入 - 使用新的
render
道具为您的复选框使用自定义渲染函数并添加一个 setValue 挂钩 - 像表单控制器 HOC 一样使用 Controller 并手动控制所有输入
避免使用控制器的例子:
https://codesandbox.io/s/optimistic-paper-h39lq
https://codesandbox.io/s/silent-mountain-wdiov
与第一个原始示例相同,但使用 yupResolver
wrapper
5.X 的说明:
这是一个不需要控制器的简化示例。不受控制是文档中的建议。仍然建议您在数据上为每个输入提供自己的 name
和 transform/filter 以删除未经检查的值,例如在后一个示例中使用 yup 和 validatorSchema,但为了示例的目的,使用相同的名称会导致将值添加到符合您要求的数组中。
https://codesandbox.io/s/practical-dijkstra-f1yox
无论如何,问题是您的 defaultValues
与复选框的结构不匹配。它应该是 {[name]: boolean}
,其中生成的 names
是 文字字符串 boat_ids[${boat.id}]
,直到它通过不受控制的表单输入,这些输入将值组合在一起成一个阵列。例如:form_input1[0] form_input1[1]
发出 form_input1 == [value1, value2]
https://codesandbox.io/s/determined-paper-qb0lf
建造 defaultValues: { "boat_ids[0]": false, "boat_ids[1]": true ... }
控制器需要布尔值来切换复选框值,并将其作为默认值提供给复选框。
const { register, handleSubmit, control, getValues, setValue } = useForm({
validationSchema: schema,
defaultValues: Object.fromEntries(
preselectedBoats.map(boat => [`boat_ids[${boat.id}]`, true])
)
});
用于 validationSchema 的模式,验证至少选择了 2 个,并在将数据发送到 onSubmit 之前将数据转换为所需的模式。它过滤掉错误的值,所以你得到一个字符串 id 数组:
const schema = Yup.object().shape({
boat_ids: Yup.array()
.transform(function(o, obj) {
return Object.keys(obj).filter(k => obj[k]);
})
.min(2, "")
});
这是一个工作版本:
import React from "react";
import { useForm, Controller } from "react-hook-form";
import FormControlLabel from "@material-ui/core/FormControlLabel";
import Checkbox from "@material-ui/core/Checkbox";
export default function CheckboxesGroup() {
const { control, handleSubmit } = useForm({
defaultValues: {
bill: "bill",
luo: ""
}
});
return (
<form onSubmit={handleSubmit(e => console.log(e))}>
{["bill", "luo"].map(name => (
<Controller
key={name}
name={name}
as={
<FormControlLabel
control={<Checkbox value={name} />}
label={name}
/>
}
valueName="checked"
type="checkbox"
onChange={([e]) => {
return e.target.checked ? e.target.value : "";
}}
control={control}
/>
))}
<button>Submit</button>
</form>
);
}
codesandbox link: https://codesandbox.io/s/material-demo-65rjy?file=/demo.js:0-932
但是,我不建议这样做,因为 material UI 中的 Checkbox 可能应该 return 检查(布尔值)而不是(值)。
我也一直在为此苦苦挣扎,这对我有用。
更新了 react-hook-form v6 的解决方案,它也可以在没有 useState
(下面的沙盒 link)的情况下完成:
import React, { useState } from "react";
import { useForm, Controller } from "react-hook-form";
import FormControlLabel from "@material-ui/core/FormControlLabel";
import Checkbox from "@material-ui/core/Checkbox";
export default function CheckboxesGroup() {
const defaultNames = ["bill", "Manos"];
const { control, handleSubmit } = useForm({
defaultValues: { names: defaultNames }
});
const [checkedValues, setCheckedValues] = useState(defaultNames);
function handleSelect(checkedName) {
const newNames = checkedValues?.includes(checkedName)
? checkedValues?.filter(name => name !== checkedName)
: [...(checkedValues ?? []), checkedName];
setCheckedValues(newNames);
return newNames;
}
return (
<form onSubmit={handleSubmit(data => console.log(data))}>
{["bill", "luo", "Manos", "user120242"].map(name => (
<FormControlLabel
control={
<Controller
name="names"
render={({ onChange: onCheckChange }) => {
return (
<Checkbox
checked={checkedValues.includes(name)}
onChange={() => onCheckChange(handleSelect(name))}
/>
);
}}
control={control}
/>
}
key={name}
label={name}
/>
))}
<button>Submit</button>
</form>
);
}
Codesandbox link: https://codesandbox.io/s/material-demo-54nvi?file=/demo.js
没有useState
完成默认选择项目的另一种解决方案:
https://codesandbox.io/s/material-demo-bzj4i?file=/demo.js
这是我的解决方案,它没有使用 Material UI 中的所有默认组件,因为在我的界面上每个收音机都会有一个图标和文本,除了不显示默认项目符号点:
const COMPANY = "company";
const INDIVIDUAL = "individual";
const [scope, setScope] = useState(context.scope || COMPANY);
const handleChange = (event) => {
event.preventDefault();
setScope(event.target.value);
};
<Controller
as={
<FormControl component="fieldset">
<RadioGroup
aria-label="scope"
name="scope"
value={scope}
onChange={handleChange}
>
<FormLabel>
{/* Icon from MUI */}
<Business />
<Radio value={COMPANY} />
<Typography variant="body1">Company</Typography>
</FormLabel>
<FormLabel>
{/* Icon from MUI */}
<Personal />
<Radio value={INDIVIDUAL} />
<Typography variant="body1">Individual</Typography>
</FormLabel>
</RadioGroup>
</FormControl>
}
name="scope"
control={methods.control}
/>;
观察:在这个例子中,我使用没有破坏的React Hook Form:
const methods = useForm({...})