钩子useCallback不是应该在每次依赖项更改时返回更新的函数吗?
我编写了这个代码沙箱,试图将我在实际应用程序中面临的问题减少到最小的可重现性示例。
import { useCallback, useState } from "react";
const fields = [
{
name: "first_name",
onSubmitTransformer: (x) => "",
defaultValue: ""
},
{
name: "last_name",
onSubmitTransformer: (x) => x.replace("0", ""),
defaultValue: ""
}
];
export default function App() {
const [instance, setInstance] = useState(
fields.reduce(
(acc, { name, defaultValue }) => ({ ...acc, [name]: defaultValue }),
{}
)
);
const onChange = (name, e) =>
setInstance((instance) => ({ ...instance, [name]: e.target.value }));
const validate = useCallback(() => {
Object.entries(instance).forEach(([k, v]) => {
if (v === "") {
console.log("error while validating", k, "value cannot be empty");
}
});
}, [instance]);
const onSubmit = useCallback(
(e) => {
e.preventDefault();
e.stopPropagation();
setInstance((instance) =>
fields.reduce(
(acc, { name, onSubmitTransformer }) => ({
...acc,
[name]: onSubmitTransformer(acc[name])
}),
instance
)
);
validate();
},
[validate]
);
return (
<div className="App">
<form onSubmit={onSubmit}>
{fields.map(({ name }) => (
<input
key={`field_${name}`}
placeholder={name}
value={instance[name]}
onChange={(e) => onChange(name, e)}
/>
))}
<button type="submit">Create object</button>
</form>
</div>
);
}这是我的密码。基本上,它呈现一个基于fields的表单。字段是包含字段特征的对象列表。其中一个特点是在用户提交表单时应用onSubmitTransformer。当用户在转换值之后提交表单时,将执行验证。我将validate封装在一个useCallback钩子中,因为它使用了转换函数之前更改的instance值。
要测试代码沙箱示例,请输入first_name输入字段并提交。
预期的行为将是在控制台中看到first_name的错误日志语句,因为转换器将将其更改为“”。
validate 的问题是似乎没有正确更新。
发布于 2021-09-15 19:05:16
这似乎是一个理解如何反应生命周期工作的问题。调用setInstance不会立即更新instance,而是在下一个呈现时更新instance。类似地,validate在下一次呈现之前不会更新。因此,在您的onSubmit函数中,您通过调用setInstance来触发一个重命名器,但是在这个呈现开始时(在onSubmitTransformer函数运行之前)使用instance的值运行validate。
修复此问题的一个简单方法是重构validate,以便它接受instance的值,而不是直接使用状态值。然后将instance上的值转换到setInstance之外。
下面是一个例子:
function App() {
// setup
const validate = useCallback((instance) => {
// validate as usual
}, []);
const onSubmit = useCallback((e) => {
e.preventDefault();
e.stopPropagation();
const transformedInstance = fields.reduce((acc, {name, onSubmitTransformer}) => ({
...acc,
[name]: onSubmitTransformer(acc[name]),
}), instance);
setInstance(transformedInstance);
validate(transformedInstance);
}, [instance, validate]);
// rest of component
}现在唯一担心的可能是使用过时版本的instance (如果instance被更新并且onSubmit在相同的呈现中被调用)。如果您关心这一点,您可以为instance添加一个参考值,并将其用于提交和验证。这样会更接近您当前的代码。
下面是使用该方法的另一个示例:
function App() {
const [instance, setInstance] = useState(/* ... */);
const instanceRef = useRef(instance);
useEffect(() => {
instanceRef.current = instance;
}, [instance]);
const validate = useCallback(() => {
Object.entries(instanceRef.current).forEach(([k, v]) => {
if (v === "") {
console.log("error while validating", k, "value cannot be empty");
}
});
}, []);
const onSubmit = useCallback((e) => {
e.preventDefault();
e.stopPropagation();
const transformedInstance = fields.reduce((acc, {name, onSubmitTransformer}) => ({
...acc,
[name]: onSubmitTransformer(acc[name]),
}), instanceRef.current);
setInstance(transformedInstance);
validate(transformedInstance);
}, [validate]);
// rest of component
}https://stackoverflow.com/questions/69197031
复制相似问题