react-hook-form watch 在渲染 select 元素后有错误的值

react-hook-form watch has wrong value after rendering select element

我在手表上使用 react-hook-form 来检测 <select> 中的变化,但是在填充 select 之后,手表最初的值是错误的。

下面是一些代码来说明问题:

import React, { useState, useEffect } from 'react';
import { useForm } from "react-hook-form";

function App() {
  const { register, watch } = useForm();
  const watchFoo = watch("foo");
  const [foos, setFoos] = useState([]);

  useEffect(() => {
    setFoos([{ id: 1, name: "one" }, { id: 2, name: "two" }]);
  }, []);

  return (
    <form>
      <select {...register('foo')}>
        {foos.map((foo, i) => (<option key={`option_${i}`} value={foo.id}>{foo.name}</option>))}
      </select>
      <p>foo is {watchFoo}</p>
    </form>
  );
}

export default App;

https://codesandbox.io/s/mystifying-kate-8n9xu

useEffect 模拟首次加载组件时从服务器获取值。

在第一次渲染时,watchFoo 具有预期的未定义值。调用 setFoos 后,它再次呈现下拉菜单的值为“one” selected,但 watchFoo 的值为“”。更改下拉的值后,watchFoo是正确的,只是初始值不对。

对此有任何解决方案或解决方法吗?

“one”值实际上并没有发生 selection 事件:它只是列表中的第一个 <option>,因此呈现的 <select> 显示了它。 select 元素本身从未触发事件以使用新的 select 框状态更新内部表单状态。这会导致内部表单状态和页面中表示的表单状态解耦,因为如果 none 个选项具有 selected [=29=,浏览器将显示第一个选项] (RFC 1866).

您可以添加一个额外的 useEffect 以在 foos 更改时更新 hookform 状态:

  const { register, watch, setValue } = useForm();

  useEffect(() => {
    if(watchFoo === "" && foos.length > 0) {
      setValue("foo", foos[0].id);
    }
  }, [watchFoo, setValue, foos])

另一个更简单的选择是只添加一个不可见的空白选项作为您的第一个条目:

<option style={{display: "none"}}></option>

这会导致您的 select 默认显示空白值,但不会在下拉列表中将空白选项作为 selection 选项保留。这会导致表单正确反映您的内部表单状态,但不会预 select 服务器的第一个选项。 Select 最适合您的用例的技术。