我得到了一个带有表单的反应组件。我把表单的设置保存在组件外的一个对象中。
const initialForm = {
name: {
elementType: 'input',
elementAtts: {
label: 'Tenant Name',
readOnly: false
},
isRequired : true,
value: '',
},
description: {
elementType: 'input',
elementAtts: {
label: 'Description',
readOnly: false
},
isRequired : false,
value: '',
}
}
const AddAndDisplay = (props) =>
{
const [formSettings, setFormSettings] = useState(initialForm);
...
}
的 elementAtts
是我传递给输入的属性。
我想做的是打开一个显示表单的模态--一次只显示,一次允许编辑--可以是编辑一个现有项目,也可以是添加一个新项目。
我这样做是为了编辑一个现有的项目和显示。
//a callback
const OpenModalForEditOrDisplay = (isEditable, cardObject) =>
{
setFormSettings(prevForm =>
{
let newForm = {...prevForm};
newForm.name.elementAtts.readOnly = !isEditable;
newForm.description.elementAtts.readOnly = !isEditable;
return {...newForm}
});
setIsFormOpen(true);
}
};
和添加一个新项目。
setFormSettings(initialForm);
setIsEditing(true);
setIsFormOpen(true); //this is merely a state saying if to show the modal with the form
用户可以提交或取消表单,无论哪种情况,我都是这样做的。
setFormSettings(initialForm);
问题是,它看起来像 initialForm
被覆盖了,如果我打开表单只显示,当我试图打开表单添加时,它就会一直显示,因为编辑部分的代码改变了我认为会是一个副本的 initialForm
. 如果我在打开编辑功能中删除这些行,表单就会保持初始表单的设置。
newForm.name.elementAtts.readOnly = !isEditable;
newForm.description.elementAtts.readOnly = !isEditable;
为什么初始表单在这里被覆盖了?
你已经使用Spread语法来克隆setFormSettings中的prevForm值。然而你必须注意,Spread语法只能浅层克隆对象,而不能深层克隆,这意味着你在prevForm中的嵌套值仍然保持着原来的引用,当你更新值时,如
newForm.name.elementAtts.readOnly = !isEditable;
newForm.description.elementAtts.readOnly = !isEditable;
你是在原始引用处突变它。正确的更新状态的方法是通过克隆每个嵌套的层级来实现状态的不变更新,就像
setFormSettings(prevForm =>
{
let newForm = {
...prevForm,
name: {
...prevForm.name,
elementAttrs: {
...prevForm.name.elementAttrs,
readOnly: !isEditable,
}
}
description: {
...prevForm.description,
elementAttrs: {
...prevForm.description.elementAttrs,
readOnly: !isEditable,
}
}
};
return newForm;
});
这是深度复制和浅层复制的问题。'formSettings'的数据源是'initialForm',使用'setFormSettings'会改变'initialForm',这是对的。使用'setFormSettings'将改变'initialForm',这是对的。因为你在初始化时使用的是浅层拷贝,你可以使用函数Deep Copy来'initialForm'。
const createInitialForm = () => ({
name: {
elementType: 'input',
elementAtts: {
label: 'Tenant Name',
readOnly: false
},
isRequired : true,
value: '',
},
description: {
elementType: 'input',
elementAtts: {
label: 'Description',
readOnly: false
},
isRequired : false,
value: '',
}
})
const AddAndDisplay = (props) =>
{
const [formSettings, setFormSettings] = useState(createInitialForm());
...
}