Chroma Panel

搜索文档

查找页面或章节

EN

React Hook Form

查看 Markdown

两种接法:用 name,或用 Controller。

React 颜色选择器与 React Hook Form 可以双向配合:让 ColorInput 通过隐藏输入框自行提交,或者用 <Controller> 包裹它,让表单掌管这个值。不需要任何适配包。

安装

npm install chroma-panel

React Hook Form 不属于 chroma-panel。下面的示例假定你的应用已经装好了它,并使用 v7 的 Controller API。

非受控:让 ColorInput 自行提交

ColorInput 传一个 name,它就会渲染一个隐藏输入框,按你设置的 format 随所在表单一起提交。React Hook Form 完全看不到这个字段,所以你需要自己从表单数据中读取:

import { useRef } from "react";
import { useForm } from "react-hook-form";
import { ColorInput } from "chroma-panel";
 
type Values = { label: string };
 
export function BrandForm() {
  const formRef = useRef<HTMLFormElement>(null);
  const { register, handleSubmit } = useForm<Values>({
    defaultValues: { label: "" },
  });
 
  const onSubmit = (values: Values): void => {
    const form = formRef.current;
 
    if (form === null) {
      return;
    }
 
    const brand = String(new FormData(form).get("brand") ?? "");
 
    save({ label: values.label, brand });
  };
 
  return (
    <form ref={formRef} onSubmit={handleSubmit(onSubmit)}>
      <input {...register("label")} />
      <ColorInput
        name="brand"
        defaultValue="#3366cc"
        aria-label="Brand color"
      />
      <button type="submit">Save</button>
    </form>
  );
}

两种方式中,这种代码更少。代价是颜色不在表单状态里:它不会出现在 formStatewatch 或 resolver 中,reset() 也不会动它。不过 form.reset() 仍会把控件恢复为 defaultValue,因为这是浏览器的原生行为。如果颜色除了 required 之外不需要其他校验,就选这种方式,ColorInput 原生支持 required,详见表单

Controller:让表单掌管这个值

<Controller> 包裹 ColorInput,它就成了一个普通的受控字段。请自己传入 valueonChange,不要直接展开 field

import { Controller, useForm } from "react-hook-form";
import { ColorInput, type ColorChangeResult } from "chroma-panel";
 
type Values = { brand: string };
 
export function BrandForm() {
  const { control, handleSubmit } = useForm<Values>({
    defaultValues: { brand: "#3366cc" },
  });
 
  return (
    <form onSubmit={handleSubmit((values) => save(values))}>
      <Controller
        name="brand"
        control={control}
        rules={{ required: "Pick a color" }}
        render={({ field, fieldState }) => (
          <div>
            <ColorInput
              value={field.value}
              onChange={(result: ColorChangeResult) =>
                field.onChange(result.hex)
              }
              onChangeComplete={() => field.onBlur()}
              aria-label="Brand color"
            />
            {fieldState.error && <p role="alert">{fieldState.error.message}</p>}
          </div>
        )}
      />
      <button type="submit">Save</button>
    </form>
  );
}

有两点需要注意:

  • 不要把 {...field} 展开到 ColorInput 上。 这样 field.onChange 收到的会是一个颜色对象,而不是你想保存的字符串;field.ref 也挂不上去,因为 ColorInput 不接受 ref
  • 这里不要给 ColorInput 设置 name 设置了 name,它就会渲染自己的隐藏输入框,这样除了表单里已有的值,颜色还会再提交一次。

field.value 是字符串,正是 ColorInput 需要的类型。它也接受 Hsva 对象,如果你不想让颜色经过十六进制的取整再保存,可以用它,两者的取舍见受控与非受控

在哪个事件上校验

拖动面板时,onChange 会持续触发;onChangeComplete 只在松手时触发一次。这个区别决定了你的校验模式会怎样表现:

useForm 模式效果
"onSubmit"(默认)提交时校验一次,无需调整。
"onBlur"在你调用 field.onBlur() 时校验。
"onChange"拖动的每一帧都会重新校验。

上面的示例在 onChangeComplete 中调用 field.onBlur(),所以在 mode: "onBlur" 下,每次拖动只校验一次,而不是每秒六十次。如果你用的是 mode: "onChange",而且表单感觉卡顿,首先就该改这里。

onChangeonChangeComplete 都和其他 prop 一起列在 ColorInput 中。