在自定义挂钩中同步状态
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
状态的初始值,并且其值在下一次渲染中不会改变。
如果你想保持form
和fields
同步,你可以使用一个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];
};
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
状态的初始值,并且其值在下一次渲染中不会改变。
如果你想保持form
和fields
同步,你可以使用一个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];
};