React 颜色选择器与 React Hook Form 可以双向配合:让 ColorInput 通过隐藏输入框自行提交,或者用 <Controller> 包裹它,让表单掌管这个值。不需要任何适配包。
安装
npm install chroma-panelReact 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>
);
}两种方式中,这种代码更少。代价是颜色不在表单状态里:它不会出现在 formState、watch 或 resolver 中,reset() 也不会动它。不过 form.reset() 仍会把控件恢复为 defaultValue,因为这是浏览器的原生行为。如果颜色除了 required 之外不需要其他校验,就选这种方式,ColorInput 原生支持 required,详见表单。
Controller:让表单掌管这个值
用 <Controller> 包裹 ColorInput,它就成了一个普通的受控字段。请自己传入 value 和 onChange,不要直接展开 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",而且表单感觉卡顿,首先就该改这里。
onChange 和 onChangeComplete 都和其他 prop 一起列在 ColorInput 中。