我正在构建一个通用的表单上下文来帮助管理我的表单状态。虽然一切都在工作,我不能得到我的错误对象初始设置在我的形式。看起来只设置了一个状态,而不是两个。从我使用过的其他组件中,我不知道在一个功能组件中只能有一个useState ?
import React, { useState } from "react";
export const FormContext = React.createContext();
// sample initialErrors looks like {password: "Error"}
const ZForm = ({ children, initialValues, initialErrors }) => {
const [errors, setErrors] = useState(initialErrors); // sets as expected.
const [form, setForm] = useState(initialValues); // initial values not set.
console.log("ZForm Intial Err:", initialErrors);
console.log("ZForm State Err:", errors); // always null!
const handleFormChange = (event, validators) => {
const { name, value } = event.target;
setForm({ ...form, [name]: value });
};
return (
<form>
<FormContext.Provider
value={{
form,
errors,
handleFormChange,
}}
>
{children}
</FormContext.Provider>
</form>
);
};
export default ZForm;
我的Login组件启动这个表单来测试
function Login() {
const [error, setError] = useState();
function handleLogin({ email, password }) {
setError({ password: "test" }); // simulate an error
}
console.log("Login Erros", error); // confirms error is set on submit.
return (
<div className="page" style={{ justifyContent: "center" }}>
<div className="inlineForm">
<h3>Login</h3>
<ZForm
initialValues={{ email: "", password: "" }}
initialErrors={error}
>
<Grid container spacing={4}>
<Grid item xs={12} md={12}>
<ZTextField label="Email" name="email"></ZTextField>
</Grid>
<Grid item xs={12} md={12}>
<ZTextField label="Password" name="password"></ZTextField>
</Grid>
</Grid>
<Grid item xs={12}>
<FormActionButtons
onSave={handleLogin}
// disabled={isLoading}
></FormActionButtons>
</Grid>
</ZForm>
</div>
</div>
);
}
export default Login;
ZForm
组件的initialState
为空,因为您没有向initialErrors
prop传递任何内容。与获取对象的initialValues
prop相反。
error
在此组件:
<ZForm
initialValues={{ email: "", password: "" }}
initialErrors={error}
>
在这一行被定义为空:
const [error, setError] = useState();
useState(initialErrors)
只在组件渲染时使用一次initialErrors
。在组件渲染完成后的任何时间更改该值都不会影响状态。在组件呈现时,initialErrors
的值等于传递给ZForm
的error
的值,即none或undefined
。
如果您将Login.js
中的useState
更改为:
const [error, setError] = useState({password: "test"});
你现在会看到{password: "test"}
设置在ZForm
的初始状态。
如果您希望能够在Login.js
和ZForm
之间共享错误,您应该从ZForm
中删除用于错误的useState
,这一行:
const [errors, setErrors] = useState(initialErrors);
相反,将Login.js
中的ZForm
组件上的initialErrors
prop称为errors
,并在组件中使用该值。
Login.js
:
<ZForm
initialValues={{ email: "", password: "" }}
errors={errors}
>
ZForm.js
:
const ZForm = ({ children, initialValues, errors }) => {
初始值表示" first"在这种情况下。它在第一次调用时被赋值给该值,并且永远被忽略在那之后,不管它是否改变。考虑:
const [number] = useState(Math.random());
number
的值永远不会改变,因为它只考虑参数一次。
在您的情况下,您有两个不同的,不同的地方存储errors
:一个在Login
中,另一个在ZForm
中。沙箱中的检测错误显示ZForm
中的setErrors
函数未使用,该函数是更新ZForm
中的值的唯一方法。
简而言之,您应该选择其中一个作为记录的来源:
使用Login
中的errors
,ZForm
将简单地接受错误作为参数并将其传递下去:
const ZForm = ({ children, initialValues, errors }) => {
const [form, setForm] = useState(initialValues);
console.log("ZForm State Err:", errors); // whatever is passed in
// ...
在ZForm
中存储错误是一个小技巧,因为要在Login
中使用它,表单必须将包裹在周围。Login
组件,而不是嵌套在其中。如果你想使用ZForm
中的一个,你必须将setErrors
添加到FormContext.Provider
值中,但请注意,由于ZForm
在Login
中呈现,除非你将其包装在其他组件中,否则你将无法在那里引用它,例如:
const ZForm = ({ children, initialValues, initialErrors }) => {
const [form, setForm] = useState(initialValues); // initial values not set.
const [errors, setErrors] = useState(initialErrors); // sets as expected.
console.log("ZForm Intial Err:", initialErrors);
console.log("ZForm State Err:", errors);
// NOT SAFE! See comments below
const handleFormChange = (event, validators) => {
const { name, value } = event.target;
setForm({ ...form, [name]: value });
};
return (
<form>
<FormContext.Provider
value={{
form,
errors,
setErrors,
handleFormChange
}}
>
{children}
</FormContext.Provider>
</form>
);
};
function Login() {
// use the `setErrors` from `ZForm`'s context
const { errors, setErrors } = useContext(FormContext);
function handleLogin({ email, password }) {
setErrors((errors) => ({
...errors,
password: "test",
})); // simulate an error
}
return (
<div className="page" style={{ justifyContent: "center" }}>
<div className="inlineForm">
<h3>Login</h3>
{/* form content goes here... */}
</div>
</div>
);
}
// Wrap `ZForm` around the `Login` form:
export default () => (
<ZForm initialValues={{ email: "", password: "" }}>
<Login />
</ZForm>
);
React中的表单很棘手。它们只是不太适合React的自顶向下模型。也有一些微妙的bug,比如handleFormChange
,它应该使用setForm
的函数参数,而不是直接引用form
(参见:https://reactjs.org/docs/hooks-reference.html#functional-updates)。我强烈建议使用库,而不是自己编写库。还有许多其他的,但我发现一个相对直观的是https://www.npmjs.com/package/react-hook-form