I have a FastField and Field group inside a component wrapped inside a FieldArray. Every change I make to any of the Fields causes rerenders in all of the fields (Including the FastFields.
Making changes in the FastField should not trigger re-renders in the Fields and other FastFields.
Here is prototype example where the issue can be seen https://codesandbox.io/s/distracted-hamilton-uf6cb?fontsize=14.
The re-renders were found when opening the page in a fullscreen tab, and checking them with the React Dev Tools. Here's a screencast of the rerenders:
https://drive.google.com/file/d/1ydVG0U8rAhzzzec0tv7k6M-X4qJlHY2B/view
The top right input for each array element and the textarea inputs are the mentioned FastFields
The form I'm working on is a rather big form. The example here is a rather basic version of it. The form is based around entries that are each stored inside the FieldArray.
The ENV is a standard codesandbox enviorment with formik added as a dependency. All is the most up to date, since the prototype was created two days ago.
I wasn't able to find any information about this but: Is there a way to ensure the individual fields of the FieldArray component would not trigger re-rendering of the the other field array fields?
Not sure whether bumping things here is punishable by death....but the issue still exists, and really is the only thing blocking me from convincing my peers to switch our form handling to formik :L
@AlanKrygowski - I was able to solve this re-render issue in a slightly unorthodox way. The issue with your code sandbox example - and with the form I was working on is that you've effectively got a piece of state in Formik that controls your entries. Then you've got your <FieldArray> component and inside that you're mapping your entries to render the various form fields. The issue is that these fields then set their own state on entries - i.e you've got:
const initialValues = {
entries: [],
}
const EntryItem = ({namePrefix}) => <Field name={`${namePrefix}.name`} component={input} />
const MyForm = () => {
return(
{/*}...Left out for brevity...*/}
<FieldArray>
{arrayHelpers => (
<>
{values.entries.map((_, idx) => (
<EntryItem
key={idx}
namePrefix={`entries.${idx}`}
/>
))}
<Button onClick={() => arrayHelpers.push("Placeholder String")}>
Add Entry
</Button>
</>
)}
</FieldArray>
);
}
Each time you type into <EntryItem /> you're triggering an update to values.entries.whatever which is causing the map to fire again and re-rendering the component.
How I solved the issue is to do the following (NB: this is just example code - actual has a lot more boilerplate):
const initialValues = {
entries: [],
entryValues: {}
}
const EntryItem = React.memo(({namePrefix}) => <Field name={`${namePrefix}.name`} component={input} />);
const MyForm = () => {
return(
{/*}...Left out for brevity...*/}
<FieldArray>
{arrayHelpers => (
<>
{values.entries.map((entry) => (
<EntryItem
key={entry}
namePrefix={`entryValues.${entry}`}
/>
))}
<Button onClick={() => {
arrayHelpers.push(values.entries[values.entries.length - 1] + 1 || 0); // This gets the last value in the array and adds 1 to prevent duplicate keys. If there are no items in the array it initializes it at 0.
setFieldValue("entryValues", {
...values.entryValues,
[values.entries.length]: "Placeholder String"
});
}}>
Add Entry
</Button>
</>
)}
</FieldArray>
);
}
So what's happening here is you're using the entries array as a place to store keys (based on the length of your entries array) for values which are stored on a separate object. Think of it as being similar to normalized state in Redux i.e. allIds, byId.
Because you're updating the values inside entryValues (which is not being mapped) the components are not re-rendered (the memo helps ensure this) except when adding to or deleting from the entries array.
You do have to do a bit of work if you want to add the ability to remove entries from the field array as well - but that basically involves passing the arrayHelpers and setFieldValue down into the EntryItem and updating the value of entryValues and your array of key indexes to remove any unneeded values.
Hope that helps!
Quick edit to cover removals - removing items from the values object is tricky - best I've come up with so far is to set the values on the keys as undefined.
I've got the same issue, every FastField in my form is fairly fast except for inside of FieldArray
Most helpful comment
@AlanKrygowski - I was able to solve this re-render issue in a slightly unorthodox way. The issue with your code sandbox example - and with the form I was working on is that you've effectively got a piece of state in Formik that controls your entries. Then you've got your
<FieldArray>component and inside that you're mapping your entries to render the various form fields. The issue is that these fields then set their own state on entries - i.e you've got:Each time you type into
<EntryItem />you're triggering an update tovalues.entries.whateverwhich is causing the map to fire again and re-rendering the component.How I solved the issue is to do the following (NB: this is just example code - actual has a lot more boilerplate):
So what's happening here is you're using the entries array as a place to store keys (based on the length of your entries array) for values which are stored on a separate object. Think of it as being similar to normalized state in Redux i.e. allIds, byId.
Because you're updating the values inside entryValues (which is not being mapped) the components are not re-rendered (the memo helps ensure this) except when adding to or deleting from the entries array.
You do have to do a bit of work if you want to add the ability to remove entries from the field array as well - but that basically involves passing the arrayHelpers and setFieldValue down into the EntryItem and updating the value of entryValues and your array of key indexes to remove any unneeded values.
Hope that helps!
Quick edit to cover removals - removing items from the values object is tricky - best I've come up with so far is to set the values on the keys as undefined.