简体   繁体   English

Material UI + React Form Hook + 多个复选框 + 默认选中

[英]Material UI + React Form Hook + multiple checkboxes + default selected

I am trying to build a form that accommodates multiple 'grouped' checkboxes using react-form-hook Material UI .我正在尝试使用react-form-hook Material UI构建一个可容纳多个“分组”复选框的表单。

The checkboxes are created async from an HTTP Request.复选框是从 HTTP 请求异步创建的。

I want to provide an array of the objects IDs as the default values:我想提供一组对象 ID 作为默认值:

defaultValues: { boat_ids: trip?.boats.map(boat => boat.id.toString()) || [] }

Also, when I select or deselect a checkbox, I want to add/remove the ID of the object to the values of react-hook-form .此外,当我 select 或取消选中一个复选框时,我想将object 的 ID添加/删除到react-hook-form的值。

ie. IE。 ( boat_ids: [25, 29, 4] ) ( boat_ids: [25, 29, 4] )

How can I achieve that?我怎样才能做到这一点?

Here is a sample that I am trying to reproduce the issue.这是我试图重现该问题的示例

Bonus point, validation of minimum selected checkboxes using Yup加分,使用 Yup 验证最小选中复选框

boat_ids: Yup.array().min(2, "")

Breaking API changes made in 6.X:破坏 6.X 中所做的 API 更改:

  • validation option has been changed to use a resolver function wrapper and a different configuration property name验证选项已更改为使用解析器 function 包装器和不同的配置属性名称
    Note: Docs were just fixed for validationResolver->resolver, and code examples for validation in repo haven't been updated yet (still uses validationSchema for tests).注意:文档只是针对validationResolver->resolver 进行了修复,并且repo 中用于验证的代码示例尚未更新(仍然使用validationSchema进行测试)。 It feels as if they aren't sure what they want to do with the code there, and it is in a state of limbo.感觉好像他们不确定他们想用那里的代码做什么,它在 state 的边缘。 I would avoid their Controller entirely until it settles down, or use Controller as a thin wrapper for your own form Controller HOC, which appears to be the direction they want to go in. I would avoid their Controller entirely until it settles down, or use Controller as a thin wrapper for your own form Controller HOC, which appears to be the direction they want to go in.
    see official sandbox demo and the unexpected behavior of "false" value as a string of the Checkbox for reference请参阅官方沙盒演示"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 no longer handles Checkbox natively ( type="checkbox" ), or to better put it, handles values incorrectly. Controller不再原生处理 Checkbox ( type="checkbox" ),或者更好地说,错误地处理值。 It does not detect boolean values for checkboxes, and tries to cast it to a string value.它不检测复选框的 boolean 值,并尝试将其转换为字符串值。 You have a few choices:你有几个选择:
  1. Don't use Controller .不要使用Controller Use uncontrolled inputs使用不受控制的输入
  2. Use the new render prop to use a custom render function for your Checkbox and add a setValue hook使用新的render道具为您的复选框使用自定义渲染 function 并添加 setValue 挂钩
  3. Use Controller like a form controller HOC and control all the inputs manually像 controller HOC 一样使用 Controller 并手动控制所有输入

Examples avoiding the use of Controller:避免使用 Controller 的示例:
https://codesandbox.io/s/optimistic-paper-h39lq https://codesandbox.io/s/optimistic-paper-h39lq
https://codesandbox.io/s/silent-mountain-wdiov https://codesandbox.io/s/silent-mountain-wdiov
Same as first original example but using yupResolver wrapper与第一个原始示例相同,但使用yupResolver包装器


Description for 5.X: 5.X 说明:

Here is a simplified example that doesn't require Controller.这是一个不需要 Controller 的简化示例。 Uncontrolled is the recommendation in the docs.不受控制的是文档中的建议。 It is still recommended that you give each input its own name and transform/filter on the data to remove unchecked values, such as with yup and validatorSchema in the latter example, but for the purpose of your example, using the same name causes the values to be added to an array that fits your requirements.仍然建议您为每个输入提供自己的name并在数据上进行转换/过滤以删除未检查的值,例如后一个示例中的 yup 和 validatorSchema,但对于您的示例而言,使用相同的名称会导致值添加到符合您要求的数组中。
https://codesandbox.io/s/practical-dijkstra-f1yox https://codesandbox.io/s/practical-dijkstra-f1yox

Anyways, the problem is that your defaultValues doesn't match the structure of your checkboxes.无论如何,问题在于您的defaultValues与复选框的结构不匹配。 It should be {[name]: boolean} , where names as generated is the literal string boat_ids[${boat.id}] , until it passes through the uncontrolled form inputs which bunch up the values into one array.它应该是{[name]: boolean} ,其中生成的names文字字符串boat_ids[${boat.id}] ,直到它通过不受控制的表单输入,这些输入将值捆绑到一个数组中。 eg: form_input1[0] form_input1[1] emits form_input1 == [value1, value2]例如: form_input1[0] form_input1[1]发出form_input1 == [value1, value2]

https://codesandbox.io/s/determined-paper-qb0lf https://codesandbox.io/s/determined-paper-qb0lf

Builds defaultValues: { "boat_ids[0]": false, "boat_ids[1]": true... }构建defaultValues: { "boat_ids[0]": false, "boat_ids[1]": true... }
Controller expects boolean values for toggling checkbox values and as the default values it will feed to the checkboxes. Controller 需要 boolean 值来切换复选框值,并将其作为默认值提供给复选框。

 const { register, handleSubmit, control, getValues, setValue } = useForm({
    validationSchema: schema,
    defaultValues: Object.fromEntries(
      preselectedBoats.map(boat => [`boat_ids[${boat.id}]`, true])
    )
  });

Schema used for the validationSchema, that verifies there are at least 2 chosen as well as transforms the data to the desired schema before sending it to onSubmit.用于 validationSchema 的模式,用于验证至少选择了 2 个,并将数据转换为所需的模式,然后再将其发送到 onSubmit。 It filters out false values, so you get an array of string ids:它会过滤掉错误值,因此您会得到一个字符串 id 数组:

  const schema = Yup.object().shape({
    boat_ids: Yup.array()
      .transform(function(o, obj) {
        return Object.keys(obj).filter(k => obj[k]);
      })
      .min(2, "")
  });

I've been struggling with this as well, here is what worked for me.我也一直在为此苦苦挣扎,这对我有用。

Updated solution for react-hook-form v6, it can also be done without useState (sandbox link below): react-hook-form v6 的更新解决方案,也可以在没有useState的情况下完成(下面的沙箱链接):

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 Codesandbox 链接: https://codesandbox.io/s/material-demo-54nvi?file=/demo.js

Another solution with default selected items done without useState : https://codesandbox.io/s/material-demo-bzj4i?file=/demo.js另一个没有useState完成默认选定项目的解决方案: https://codesandbox.io/s/material-demo-bzj4i?file=/demo.js

Here is a working version:这是一个工作版本:

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代码沙盒链接: https://codesandbox.io/s/material-demo-65rjy?file=/demo.js:0-932

However, I do not recommend doing so, because Checkbox in material UI probably should return checked (boolean) instead of (value).但是,我不建议这样做,因为 Material UI 中的 Checkbox 可能应该返回选中的(布尔值)而不是(值)。

Here's my solution, which is not using all the default components from Material UI cause at my interface each radio will have an icon and text, besides the default bullet point not be showed:这是我的解决方案,它没有使用 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}
/>;

Observation : At this example I use React Hook Form without destruct:观察:在这个例子中,我使用没有破坏的 React Hook Form:

const methods = useForm({...})

This is my solution with react hook form 7, the other solutions don't work with reset or setValue.这是我使用 react hook form 7 的解决方案,其他解决方案不适用于 reset 或 setValue。

<Controller
      name={"test"}
      control={control}
      render={({ field }) => (
        <FormControl>
          <FormLabel id={"test"}>{"label"}</FormLabel>
          <FormGroup>
            {items.map((item, index) => {
              const value = Object.values(item);
              return (
                <FormControlLabel
                  key={index}
                  control={
                    <Checkbox
                      checked={field.value.includes(value[0])}
                      onChange={() =>
                        field.onChange(handleSelect(value[0],field.value))
                      }
                      size="small"
                    />
                  }
                  label={value[1]}
                />
              );
            })}
          </FormGroup>
        </FormControl>
      )}
    />

link to codesandbox: Mui multiple checkbox codesandbox 链接: Mui 多复选框

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM