I pass in props to a function component:
const [accountForm, setAccountForm] = useState({})
const syncForm = (subForm, name) => {
const form = {...accountForm, [name]: subForm}
setAccountForm(form)
}
<>
<ComponentA handler={syncForm} form={accountForm.objA} />
<ComponentB handler={syncForm} form={accountForm.objB} />
<ComponentC handler={syncForm} form={accountForm.objC} />
</>
The Components (A, B, & C) are written similarly like so:
const ComponentA = ({form, handler}) => {
const handler = () {
handler()
}
return (
<Form onChange={handler}/>
)
}
const areEqual = (n, p) => {
const pForm = p.form;
const nForm = n.form;
const equal = pForm === nForm;
return equal;
}
export default React.memo(ComponentA, areEqual)
I use a memo because Components A, B and C have the same parent, but I only want to rerender A, B or C when their subform has changed, so I don't want validations to run on other forms.
When I fill out form A, all the values are stored correctly. When I fill out a different form such as B or C, form A reverts to an empty object {}.
However, if I change the state to 3 objects instead of a nested object, it works:
const [objA, setObjA] = useState({});
const [objB, setObjB] = useState({});
const [objC, setObjC] = useState({});
<>
<ComponentA form={objA} />
<ComponentB form={objB} />
<ComponentC form={objC} />
</>
Why doesn't the nested object approach work?
This seems to be a stale enclosure over the accountForm
state. Looks like the memo
HOC and areEqual
function prevent the children components from rerendering and receiving a handler
callback with updated state.
Use a functional state update to correctly update from any previous state instead of whatever is closed over in any callback scope.
const syncForm = (subForm, name) => {
setAccountForm(form => ({
...form,
[name]: subForm
}));
}