在自定义挂钩中同步状态

Synchronizing state in custom hooks

TL/DR:
如何正确同步两个钩子之间的状态?

Implementation detail:

I'm gonna simplify the example and use useState, instead of using the first custom hook I'm actually using in order to simplify the question.

Essentially what the omitted hook does is it returns a state and helper functions. So just to paint the picture:

const [email, onEmailChange, onEmailBlur] = useField('init', isEmailValid)

replaced in question by simply (although it's technically redundant here)

 const [email, setEmail] = useState('hi')

The reason why I'm leaving this excerpt here is so I don't get comments/answers that the initial useState is redundant

有效方法:
将状态引用传递给简单地 returns 它的基本函数。

const useForm = (...fields) => {
  return {
    fields,
  }
}
const form = useForm(email) // updates correctly

问题从哪里开始
现在显然这东西甚至不需要钩子。当我尝试添加更复杂的逻辑时出现问题

const useForm = (submitFunction, ...fields) => {
  const initialForm = {
    fields,
    wasSubmitted: false,
  }
  const [form, setForm] = useState(initialForm)

  const handleSubmit = (event) => {
    event.preventDefault()
    if (!fields.some(({ error }) => !!error) {
       submitFunction()
    }
    setForm({
      fields,
      wasSubmitted: true,
    })
  }

  return [form, handleSubmit] // now the state no longer updates
}

Now I do have a suspicion as to why this is happening. Simply because useState initializes a new instance of the state, so we are simply returning the initalized values from the passed states.

问题是,我无法真正思考如何同步这些。

具有工作和非工作钩子的可编辑示例:

正如您提到的,这是因为 useForm 中的 useState 正在设置 form 状态的初始值,并且其值在下一次渲染中不会改变。

如果你想保持formfields同步,你可以使用一个useEffect来更新form的值,每次fields中的一个值=] 变化。

...
const [form, setForm] = useState<Form>(initialForm);

useEffect(() => {
  setForm(formState => ({ ...formState, fields }))
}, fields);
...

此解决方案的问题在于,每当 fields 中的值发生变化时,useEffect 中的 setForm 会导致另一次重新呈现。

解决此问题的另一种方法是不在状态中保存 fields 值,只将 wasSubmitted 保留在 useForm.

的状态中
const useForm = (submitFunction, ...fields) => {
  const [wasSubmitted, setWasSubmitted] = useState(false);

  const handleSubmit = (event) => {
    if (fields.some(field => field === "run")) {
      submitFunction();
    }
    setWasSubmitted(true);
  };

  return [{ fields, wasSubmitted }, handleSubmit];
};