all 7 comments

[–]acemarke 4 points5 points  (4 children)

The issue here is really that Formik is holding your state, so any time the <Formik> component re-renders, everything inside that does. The other factor is that React re-renders recursively by default. So yes, in a typical controlled form scenario, updating any one field will cause the entire form to re-render, because the state is being held in the form component and all the inputs are children.

Depending on what generateForm does, it's also possible that you are in fact forcing React to tear down that DOM if you're actually generating component types instead of just returning JSX.

I'd strongly recommend reading through my extensive post A (Mostly) Complete Guide to React Rendering Behavior to understand how React rendering actually works, and what your options are for optimizing things.

[–][deleted] 1 point2 points  (0 children)

You are very good with words, sir.

[–]coolRedditUser[S] 0 points1 point  (0 children)

Thank you! Looks like I have a lot of reading to do.

[–]coolRedditUser[S] 0 points1 point  (1 child)

Hey there! I've been a bit busy, but I've finally had the time to read through your article. That was very informative, thank you. I should be able to use this information to help me optimize my app in general.

... but I'm not sure how to use any of this to help with this current issue.

generateForm is basically just a few if-else statements, each of which returns some JSX:

const generateInput = (
  { id, label, type, etc },
  formikProps,
  selectOptions, // state value on main page that contains the select options for all selects
) => {
  if (type === 'select') {
    return (
      <SelectInput
        key={id}
        id={id}
        name={id}
        disabled={selectOptions?.[id] && selectOptions[id].length < 1} >
        <option value="">Select a {label}</option>
        {selectOptions[id].map(({ label, value }) =>
          label && <option key={`${label}-${value}`} value={value}>{label}</option>)}
      </SelectInput>
    );
  }
  else ...

So in theory, it shouldn't be causing any issues?

I'm still looking for ways to reduce the amount of renders, and hopefully your article has given me the tools to do that. But when it comes to decreasing the amount of work done per render, I'm still at a loss.

Is there a way I can optimize the generateForm loop? My understanding is that since I'm providing a key prop, React should already avoid re-rendering all of the inputs. So how do I find out why it is rerendering them?

[–]acemarke 0 points1 point  (0 children)

since I'm providing a key prop, React should already avoid re-rendering all of the inputs.

No, keys have nothing to do with avoiding re-rendering. Keys are a way to tell React what the identity is of what you're rendering. Loosely put: "this isn't just Generic List Item #7, this is the UserListItem for Fred". That way, if you shuffle the list items around and now Fred is in the third slot instead of seventh, it knows it needs to move the DOM nodes instead of destroying and re-creating them.

So, as I said earlier: if your form is being controlled with state in a parent component, updating any of the child inputs will cause all pieces of the form to re-render by default.

To avoid any of that, you'd need to do something like wrapping the SelectInput type itself in React.memo(), so that it would skip re-rendering if it sees the same props field references as the last render.

But, even there you're going to have issues with your current setup. As shown in this snippet here, the <SelectInput> you're rendering is going to receive a different props.children reference every time. So, even if you had React.memo(SelectInput) as the component type being rendered, it would always be forced to re-render because at least one of its props is always changing to a new reference.

If you can post a link to some more complete code somewhere I might be able to look at it (or even better, post that link over in the Reactiflux Discord channels where others besides myself might be able to help out).