I need to create a form with multiple subforms. Updating a property should not update the subforms. I'm using React.memo to achieve this.
However, the values on the form disappear whenever a property in the subform is changed.
For example, if I enter 'test' in the first field and then 'test' in the second field, the value in the first field disappears.
I believe my current approach is not correct. How can I address this issue?
import React, {useState} from "react";
const ChildA = ({onChange, initForm}) => {
const [form, setForm] = useState({...initForm})
const update = e => {
let newForm = {...form, name: e.target.value}
setForm(newForm)
onChange('childA', newForm)
}
return <>
<input value={form.name} onChange={e => update(e)} />
(Updated at : {new Date().toLocaleTimeString([], { timeStyle: "medium" })})
</>
}
const MemoChildA = React.memo(ChildA, () => true)
const ChildB = ({onChange, initForm}) => {
const [form, setForm] = useState({...initForm})
const update = e => {
let newForm = {...form, name: e.target.value}
setForm(newForm)
onChange('childB', newForm)
}
return <>
<input value={form.name} onChange={e => update(e)} />
(Updated at : {new Date().toLocaleTimeString([], { timeStyle: "medium" })})
</>
}
const MemoChildB = React.memo(ChildB, () => true)
function Parent() {
const [form, setForm] = useState({name:'', childA:{name: null}, childB:{name: null}})
const change = (key, value) => {
setForm({...form, [key]: value})
}
return <>
<div>
Parent Name : <input value={form.name} onChange={e => setForm({...form, name: e.target.value})} />
(Updated at : {new Date().toLocaleTimeString([], { timeStyle: "medium" })})
</div>
<div>
ChildA Name : <MemoChildA onChange={change} form={{...form.childA}}/>
</div>
<div>
ChildB Name : <MemoChildB onChange={change} form={{...form.childB}}/>
</div>
</>
}
export default Parent;
Using
React.memo()is an optimization - you should not rely on it and indeed the documentation explicitly says:If you remove the
useMemo, the problem with child updates erasing the parent state disappear, so that's step 1.You've already got each child looking after its own
state, but this is then duplicated in the parent in this structure:Duplication of state is a major cause of bugs and needs to be eliminated, even before we consider any problem with excessive re-rendering.
There's also the anti-pattern of "mirroring
propsinstate" here:The children are written as if
initFormwill only ever be their initial value - butinitFormwill actually be changing all the time.If we declare the parent as the owner (or "single source of truth") of all form state (and this will make getting accurate data out of it much easier later) we get much less code and (IMO) it's far easier to follow:
You'll note that each sub-form is re-rendering on each change. Now that it's working properly, you could consider re-adding
React.memowith a suitablearePropsEqualtest. But make sure you're not over-estimating the cost of a re-render. Is it worth it, really?