我认为这里的主要问题是您当前的实现替换了现有状态,每次更改,因此不可能知道是否/发生了什么变化。甚至用于保存前一个状态值的“usePrevious”钩子在这里都不起作用,因为它只会保存上一个字段编辑,而不是原始值,以进行比较。许多表单处理解决方案通过保持未变异的初始状态或跟踪“脏”字段和/或更多来处理此问题。
这是我的一个建议,可以调整您的表单上下文状态以跟踪已更新的字段。 dirtyFields 对象保存原始值。
表单提供者
const [formValue, setFormValue] = useState({
basicDetails: {
fields: {
firstName: 'John',
lastName: '',
},
dirtyFields: {},
},
companyDetails: {
fields: {
companyName: 'Some Company',
designation: 'Some Designation',
},
dirtyFields: {},
},
});
基本细节
const BasicDetails = () => {
const [value, setValue] = React.useContext(FormContext);
const {
basicDetails: { fields }, // <-- get field values
} = value;
const handleInputChange = (event) => {
const { name, value } = event.target;
setValue((prev) => ({
...prev,
basicDetails: {
...prev.basicDetails,
fields: {
...prev.basicDetails.fields,
[name]: value,
},
...(prev.basicDetails.dirtyFields[name] // <-- only mark dirty once with original value
? {}
: {
dirtyFields: {
...prev.basicDetails.dirtyFields,
[name]: prev.basicDetails.fields[name],
},
}),
},
}));
};
return (
<>
<div className="form-group col-sm-6">
<label htmlFor="firstName">First Name</label>
<input
...
value={fields.firstName} // <-- access fields object
...
/>
</div>
<div className="form-group col-sm-4">
<label htmlFor="lastName">Last Name</label>
<input
...
value={fields.lastName}
...
/>
</div>
</>
);
};
就业详情
const EmploymentDetails = () => {
const [value, setValue] = React.useContext(FormContext);
const {
companyDetails: { fields },
} = value;
const handleInputChange = (event) => {
const { name, value } = event.target;
setValue((prev) => ({
...prev,
companyDetails: {
...prev.companyDetails,
fields: {
...prev.companyDetails.fields,
[name]: value,
},
...(prev.companyDetails.dirtyFields[name]
? {}
: {
dirtyFields: {
...prev.companyDetails.dirtyFields,
[name]: prev.companyDetails.fields[name],
},
}),
},
}));
};
return (
<>
<div className="form-group col-sm-6">
<label htmlFor="companyName">Company Name</label>
<input
...
value={fields.companyName}
...
/>
</div>
<div className="form-group col-sm-4">
<label htmlFor="designation">Designation</label>
<input
...
value={fields.designation}
...
/>
</div>
</>
);
};
在递增/递减步长时检查脏字段。
为每个部分指定一个与表单上下文中的“表单键”匹配的 id。
const sections = [
{
title: 'Basic Details',
id: 'basicDetails',
onClick: () => setCurrentPage(1),
},
{
title: 'Employment Details',
id: 'companyDetails',
onClick: () => setCurrentPage(2),
},
{ title: 'Review', id: 'review', onClick: () => setCurrentPage(3) },
];
创建一个checkDirty 实用程序。这里我只是简单的记录下脏字段
const checkDirty = (page) => {
console.log('check dirty', 'page', page);
console.log(
value[sections[page - 1].id] && value[sections[page - 1].id].dirtyFields,
);
};
const next = () => {
setCurrentPage((prev) => prev + 1);
checkDirty(currentPage); // <-- check for dirty fields when updating page step
};
const prev = () => {
setCurrentPage((prev) => prev - 1);
checkDirty(currentPage);
};
由于表单上下文中存在额外的嵌套状态,这里有一个实用程序可以将其还原为只是您希望在审阅步骤中呈现的表单数据。
const prettyReview = sections.reduce(
(sections, section) => ({
...sections,
...(value[section.id]
? { [section.id]: { ...value[section.id].fields } }
: {}),
}),
{},
);
...
<pre>{JSON.stringify(prettyReview, null, 2)}</pre>
编辑
您说您的数据来自后端 API 调用。这是一个上下文状态初始化函数,它将 API 数据形状映射到我拥有的状态形状。
来自 API 的给定数据
const apiData = {
basicDetails: {
firstName: 'John',
lastName: '',
},
companyDetails: {
companyName: 'Some Company',
designation: 'Some Designation',
},
};
初始化函数
const initializeContext = (data) =>
Object.entries(data).reduce(
(sections, [section, fields]) => ({
...sections,
[section]: {
fields,
dirtyFields: {},
},
}),
{},
);
初始化FormProvider上下文状态
function FormProvider({ children }) {
const [formValue, setFormValue] = useState(initializeContext(apiData));
return (
<FormContext.Provider value={[formValue, setFormValue]}>
{children}
</FormContext.Provider>
);
}