Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to avoid rerendering? #342

Closed
SuperOleg39 opened this issue Jan 12, 2018 · 81 comments
Closed

How to avoid rerendering? #342

SuperOleg39 opened this issue Jan 12, 2018 · 81 comments

Comments

@SuperOleg39
Copy link

Question

Hello!

Formik library have built-in tools to avoid rerendering all form fields, after changing one of this fields?

@joseandresromero
Copy link

I'm having the same issue. Typing inside a field is very slow in a form that contains 10 fields, even more with React Native when you create a custom field component with several children components and it internally uses the setFieldValue on a TextInput. This is because every time that a key is entered all the children components of the Formik component are re-rendered. Is there any way to avoid this?

@HPieters
Copy link

I am also having the same question/issue, although not running into any visible performance issues during development.

@eonwhite
Copy link
Collaborator

While performance is rarely an issue on forms, I've occasionally hit this issue on really big/complex forms with custom nested components.

The way I've gotten around it is making a form field component that tracks its own value state (setting only its own state onChange), then calls Formik's setFieldValue onBlur, instead of onChange. Then the re-render on a keystroke is contained to that field component rather than the whole form, while the value change is propagated to the rest of the form when the field loses focus.

@jaredpalmer
Copy link
Owner

I actually have a more agressively optimized version of Field that uses shouldComponentUpdate prevents rerenders on “vanilla” inputs (i.e where the component is a string). I think it’s time to write that up in a recipe and add it to core

@gunn4r
Copy link
Contributor

gunn4r commented Jan 30, 2018

@jaredpalmer That would be fantastic. I'm running into this issue myself using a bunch of custom components (and lots of React-Selects). In particular the the repainting of the react-select elements is obnoxious because the little X and Toggle chevrons blink on every repaint. :(

+1 for a more optimized <Field /> 👍

Edit: My issue had nothing to do with Formik. Still +1 for more optimization :)

@ikhsanalatsary
Copy link

In react native I do this.

export default class FormikTextInput extends React.Component<
  Props,
  $FlowFixMeState
> {
  props: Props
  state: $FlowFixMeState

  state = {
    value: ''
  }

  handleChange = (value: string) => {
    if (this.state.value !== value) {
      // remember that onChangeText will be Formik's setFieldValue
      this.props.onChangeText(this.props.name, value)
      this.setState({ value })
    }
  }

  render () {
    // we want to pass through all the props except for onChangeText
    const { onChangeText, icon, wrapperStyle, ...otherProps } = this.props
    return (
      <View
        style={[
          icon && { flex: 1, flexDirection: 'row' },
          wrapperStyle && wrapperStyle
        ]}
      >
        <FormInput
          onChangeText={this.handleChange}
          {...otherProps} // IRL, you should be more explicit when using TS
        />
        {icon && icon}
      </View>
    )
  }
}

@jaredpalmer
Copy link
Owner

You can try this out with Formik 1.0.0-alpha.1's new component

@ikhsanalatsary
Copy link

@jaredpalmer can use it in react native?

@ghost
Copy link

ghost commented Feb 22, 2018

@jaredpalmer Thanks it's working smoothly now

@jaredpalmer
Copy link
Owner

Not yet. Let me make an issue for that.

@jaredpalmer
Copy link
Owner

npm i formik@next

@kevinwolfcr
Copy link

I also have performance issues, when using yup validations when I try to type really fast. Any chance to add a debounce on the handleChange function?

@benjamingeorge
Copy link

benjamingeorge commented Jul 25, 2018

I'm experiencing this issue as well with 1.0.2. I was migrating away from mobx-react-form because I liked the render props of Formik but this re-render performance is definitely an issue. It's not an issue with mobx-react-form probably because MobX updates are so optimized. I'm using custom Inputs like material-ui so your note "I actually have a more agressively optimized version of Field that uses shouldComponentUpdate prevents rerenders on “vanilla” inputs " mentioned above won't work unless I'm missing something.

@ianemv
Copy link

ianemv commented Aug 7, 2018

This is still happening with RN. Using the ff.

"formik": "^1.0.2",
"native-base": "^2.7.2",
"react": "16.3.1",
"react-native": "^0.55.2",

@sivarmn
Copy link

sivarmn commented Aug 9, 2018

Thanks.

Have the same issue with Field as well as Fastfield even with the latest version. My form has some 30 fields (Text inputs, Selects and radiogroup) and there is a serious lag for each keystroke.

image

image

@you-fail-me
Copy link

having same issue, formik 0.11.11 yup 0.24.1, form has like 5 inputs and things are very sluggish. Upgrading formik didn't make any difference. Is there a way to use debounce with formik? It's very needed feature...

@Pjata
Copy link

Pjata commented Aug 15, 2018

I have the same issue, tried memoizing the error object but Formik seems to update the error on all fields.

@modosc
Copy link

modosc commented Aug 17, 2018

same thing here with the most current formik / yup - every keypress causes all inputs to rerender when using schemaValidation but using field-level validations don't. should we open a new issue?

@jaredpalmer
Copy link
Owner

I think so

@beeant
Copy link

beeant commented Aug 17, 2018

I'm having this problem too..

@Proxiweb
Copy link

Same here

@harunurhan
Copy link

Unsurprisingly, it is significantly faster with prod build but even then UX is annoying for mid or big size forms.

@noinkling
Copy link

noinkling commented Aug 28, 2018

I've been looking at the runValidations function:

formik/src/Formik.tsx

Lines 265 to 285 in a35fba4

runValidations = (
values: FormikValues = this.state.values
): Promise<FormikErrors<Values>> => {
this.setState({ isValidating: true });
return Promise.all([
this.runFieldLevelValidations(values),
this.props.validationSchema ? this.runValidationSchema(values) : {},
this.props.validate ? this.runValidateHandler(values) : {},
]).then(([fieldErrors, schemaErrors, handlerErrors]) => {
const combinedErrors = deepmerge.all<FormikErrors<Values>>(
[fieldErrors, schemaErrors, handlerErrors],
{ arrayMerge }
);
if (this.didMount) {
this.setState({ isValidating: false, errors: combinedErrors });
}
return combinedErrors;
});
};

Formik could avoid extra renders if:

  1. The isValidating state was dropped, and...
  2. A deep comparison was done with the previous error object, and only calling setState if they differ.

The obvious downsides are:

  1. isValidating is probably too useful to some people to just remove outright.
  2. There are potentially performance concerns with doing a deep comparison. For the vast majority of cases it would probably be worth it to avoid unnecessary re-renders, especially since it would be in an async context, but maybe there are edge cases where people have super complex or frequently changing error objects.

For these reasons, maybe we could be given the ability to provide our own function to override some or all of the functionality of runValidations? Essentially we just need some sort of hook that gives us the ability to avoid the internal setState calls if we need to.

The alternative is for us to do the errors comparison in shouldComponentUpdate on our rendered component, but if the error objects match you still also have to compare all the other props - the mechanism doesn't really sit at the right level, it's acting too late.

Anyway, just trying to throw some thoughts out there. The other thing that could be done, of course, is just to have synchronous validation, which I know has been considered. But that isn't exactly ideal either, because it would be render-blocking. Maybe we just need to wait for Suspense...

@paolostyle
Copy link

Any progress on this? Still have serious issues on a very small form (5 inputs and a select), I set validateOnChange and validateOnBlur to false as I don't really need this and it helps a bit but it's still not very performant when someone is typing very fast... I'm using validationSchema and I'd rather not change that.

@Arthur-Conan-Dog
Copy link

@evark Hi there, any chance you could share your solution via codesandbox? 👀

@dacopan
Copy link

dacopan commented Jun 18, 2020

@evark Hi there, any chance you could share your solution via codesandbox?
Just use FastField instead of Field

@Arthur-Conan-Dog
Copy link

@dacopan I did try FastField in my scenario, however it doesn't prevent other unrelated fields from re-rendering. Check this
Toggle "Highlight updates when components render" on in React DevTools, choose one input to change, and you will find that all inputs are highlighted.

@johnfrommartin
Copy link

@Arthur-Conan-Dog I would make sure you're passing the value prop directly to the fast field, instead of values. values will change if any of those children change.

@lagoasoft-lucasschmidt
Copy link

My suggestion is to simply not use formik v2 ... I didnt test the performance on the new version. I used the milestone version and I wasnt satisfied as well with the architecture choices, but maybe it got better.

The problem is that, if you have all the form data in a CONTEXT ... you will always re-render everything ... so if you have a huge form, super complex etc, you will be screwed. To simply add memo and stuff to everything, it is just bad in cases of huge forms that involve complex features like modals and such. Imagine an amazon payment wizard process using formik, you would never use it.

I dont know if the new v3 version improved this ... but I created a form wrapper on redux, it simply works, and no issues. I believe there are more libraries that use redux ... this way you avoid re-renders. I went on this path in order to integrate better with rx redux observable as well.

@johnrom
Copy link
Collaborator

johnrom commented Jun 18, 2020

@lucastschmidt there are definite downsides to using Context. Formik is following the path of React itself. There are other packages that do things like use Redux and other tools to manage form state, but we're sticking with straight React hooks. As of right now, the rendering problem is known and we haven't found a way around it without:

  • React releasing a state slicing mechanic like Context Selectors
    • There are some experimental options there, but none of them are suitable for inclusion in Formik's users' production code given their non-official status.
  • Implementing the bulk of state management at Field level and not consuming formik.values from Field.
    • This is the basis of Formik v3, and will require some duplication of state to keep Form state in sync with Field state.

I'd like to add that many of the performance issues with re-rendering can be mitigated by using hooks correctly (avoiding objects/functions which are regenerated on every render), and splitting forms into smaller chunks or pages (less to re-render per page). Memoizing things that otherwise wouldn't need memoizing should be the last resort, and is only usually necessary when the fields themselves are extremely expensive.

The "promise" of context and generally sticking with "vanilla" React is that React itself will eventually optimize and abandon renders that do not result in any changes with concurrent mode, speculative rendering, or whatever method they end up implementing. Sadly, that hasn't yet manifested.

@zeabdelkhalek
Copy link

+1, this issue should be re-opened again .. I'm facing a huge performance issue in react native.

@dmrugalski
Copy link

Based on ideas from this thread and many others from whole internet I'm using this kind of solution for that problem:

I wrote a simple "wrapper" for each field, like that:

<SimpleField
                label={'Field Label'}
                additionalLabel={'Help text'}
                field={'brand'}
                value={values.brand}
                error={errors.brand}
                touched={touched.brand}
                handleChange={handleChange}
              />

and my SimpleField component looks like that:

<CondenseTextField
        label={props.label}
        id={props.field}
        type={props.type || "string"}
        fullWidth={true}
        color="secondary"
        value={value}
        error={!!props.error && props.touched}
        helperText={props.error && props.touched ? props.error : null}
        onChange={onChange}
        multiline={props.multiline}
        rows={props.multiline ? 3 : 0}
        autoComplete={props.autocomplete ? 'on' : 'false'}
      />
      {props.additionalLabel && <AdditionalLabel>{props.additionalLabel}</AdditionalLabel>}

I'm using react material UI so some tags are custom created as styled (ex. CondenseTextField)
How do this work?
SimpleField component is memoized as

export default React.memo(SimpleField);

and as long as we pass primitive values to component, that our field is not re-rendered.

Moreover, I've added debounce function for onChange callback:

const [value, setValue] = useState(props.value);
  const [t, setT] = useState(undefined);
  const onChange = e => {
    e.persist();
    setValue(e.target.value);
    if (t) clearTimeout(t);
    // @ts-ignore
    setT(setTimeout(() => {
      props.handleChange(e);
    }, 500));
  };

so as you can see I'm calling handleChange event from useFormik() 500ms after last keypress (input value) into a field.
That way my form has >200 fields and it's fully useful, for me. Still, there is a small delay (it's less that 100ms after each handleChange) but it's not visible for user (especially in PROD build, using both, desktop and mobile devices :)).
Also, created the same kind of wrapper for radiobutton group and select controls :)
Works like a charm.
Happy Coding! :)

@zeabdelkhalek
Copy link

I have migrated to React hook form and I'm totally satisfied with the results.

@Cuantico
Copy link

does anyone know if this issue had a solution? FastField is not a way to go for me, my Fromik implementation needs to be the useFormik hook, is there a way to cut that lag on each keystroke?

@AlanKrygowski
Copy link

@Cuantico Tbh did the same approach as Adbel and moved to React Hook Form. Tried my best to make Formik listen, but I guess due to the form state being kept in a single variable, the rerenders still take place

@lagoasoft-lucasschmidt
Copy link

Yep @Cuantico I created my own custom lib using redux, but we only used internally, not 100% stable .. hopefully one day we can open source when we have time ... but ideally there are multiple good libraries out there.

Check the source code, and the issues open before migrating, to be sure it is not context based to store info.

@Cuantico
Copy link

Cuantico commented Sep 26, 2020

@lucastschmidt @AlanKrygowski thanks, finally I could use the FastField and pass it my own Input component in the props: , so I changed the useFormik to Formik implamentation and include the validateOnChange and the validateOnBlure to false, also validateOnMount to true in order to run it at low priority. the performance now is acceptable.

@emmanuelsio
Copy link

Formik is telling the form to render in every change. When you press a key, the context change, so Formik ask to all controls render again, and it is ok. This brings some performance issues if you not do anything. So you can use in your components shouldComponentUpdate() where you decide if no changes has made in control, to not render again. This way performance issues dissapears, because you avoid rendering. Another way is to use Memo or PureComponents.

@pietmichal
Copy link

Setting validateOnChange to false when using Yup brought acceptable level of performance back

I've noticed in the flame graph that for some reason, when using Yup and validateOnChange, the form is re-rendered 4 times in a row.

@CPedrini
Copy link

CPedrini commented Mar 3, 2021

* Implementing the bulk of state management at Field level and not consuming `formik.values` from Field.
  
  * This is the basis of Formik v3, and will require some duplication of state to keep Form state in sync with Field state.

I am on the process of overhauling the form system of our platform baseline and Formik would likely be a good fit, however, we need to support really complex form scenarios.

So right know I find that we have two deal-breakers:

  1. This Context issue.
  2. There is no way to do any field with validation with custom dependencies (although we can circumvent this other problem with custom validations).

@johnrom: I know that there is another issue tracking (2), but looks like it is not being addressed at the moment, right?

Also, would you mind giving us an update on how v3 is going?

Thanks!

@johnrom
Copy link
Collaborator

johnrom commented Mar 3, 2021

I don't know anything about 2. Jared has done a useContextSelector implementation for v3: #2846 and I've implemented a version which uses Context only for the API which accesses an underlying subscription: #2931 but need to create a fresh PR to focus just on the state changes.

@CPedrini
Copy link

CPedrini commented Mar 5, 2021

Oh, I see that you now use use-context-selector. I'll give it a try on the next branch, but this sounds very promising! We will definitively be keeping an eye on this and once v3 is more stable we may even consider some kind of extension for the point 2 that I mentioned about the validations.

Amazing job, really appreciated. Thanks!

@johnrom
Copy link
Collaborator

johnrom commented Mar 5, 2021

Yep! use Context Selector is great but unfortunately it doesn't provide any optimizations or render safety (esp. in concurrent mode) outside of the Context children so it is limited in its efficacy. The question that remains is whether that limitation matters.

@shamim-apex
Copy link

I am using formik with react bootstrap form. having the same issue. Any solution?

@johnrom
Copy link
Collaborator

johnrom commented Mar 22, 2021

@shamim-apex a complete solution for v3 is being actively worked on here: #3089

@Cuantico
Copy link

I am using formik with react bootstrap form. having the same issue. Any solution?

move to react-hook-form and material-ui

@v1adko
Copy link

v1adko commented Mar 23, 2021

move to react-hook-form and material-ui

Advice for people researching which libs to use and interested in react-hook-form - look into something like Tailwind instead of Material UI. Material uses controlled components while rhf relies on internal component state and context. You will have to wrap all of your input elements in a Control component in order to make it usable which adds a noticeable layer of complexity.

@ChristianBermas
Copy link

You can try in formik SetFieldValue('fieldname', value, false) you can set shouldvalidate to false

@valerii15298
Copy link

Hello, guys. Having the same problem. Using nested fields. The problem is all form(and all fields) rerenders on every keystroke. The possible solution I think is to add possibility optionally rerender form if value of some field changes.

@no12345678
Copy link

Based on ideas from this thread and many others from whole internet I'm using this kind of solution for that problem:

I wrote a simple "wrapper" for each field, like that:

<SimpleField
                label={'Field Label'}
                additionalLabel={'Help text'}
                field={'brand'}
                value={values.brand}
                error={errors.brand}
                touched={touched.brand}
                handleChange={handleChange}
              />

and my SimpleField component looks like that:

<CondenseTextField
        label={props.label}
        id={props.field}
        type={props.type || "string"}
        fullWidth={true}
        color="secondary"
        value={value}
        error={!!props.error && props.touched}
        helperText={props.error && props.touched ? props.error : null}
        onChange={onChange}
        multiline={props.multiline}
        rows={props.multiline ? 3 : 0}
        autoComplete={props.autocomplete ? 'on' : 'false'}
      />
      {props.additionalLabel && <AdditionalLabel>{props.additionalLabel}</AdditionalLabel>}

I'm using react material UI so some tags are custom created as styled (ex. CondenseTextField) How do this work? SimpleField component is memoized as

export default React.memo(SimpleField);

and as long as we pass primitive values to component, that our field is not re-rendered.

Moreover, I've added debounce function for onChange callback:

const [value, setValue] = useState(props.value);
  const [t, setT] = useState(undefined);
  const onChange = e => {
    e.persist();
    setValue(e.target.value);
    if (t) clearTimeout(t);
    // @ts-ignore
    setT(setTimeout(() => {
      props.handleChange(e);
    }, 500));
  };

so as you can see I'm calling handleChange event from useFormik() 500ms after last keypress (input value) into a field. That way my form has >200 fields and it's fully useful, for me. Still, there is a small delay (it's less that 100ms after each handleChange) but it's not visible for user (especially in PROD build, using both, desktop and mobile devices :)). Also, created the same kind of wrapper for radiobutton group and select controls :) Works like a charm. Happy Coding! :)

Hey it seems like you really solved it, can you by any chance share a demo- github repo/stackblitz/anything so I can see how you constructed your form and everything because I'm stuck on this issue for a long time and have a really long form and it would help alot if you can share with me :)

@AlonMiz
Copy link

AlonMiz commented Jul 17, 2022

the main issue with formik creating lots of renders is how it calculates the state of the form when validating.
the issue is when a user types a "character", 3 renders happen:

  1. form values change
  2. state is changed to "isValidating:true"
  3. state is changed to "isValidating:false"

these renders are causing terrifying issues with performance on large forms especially when a user types a lot of characters.

I've made a gist with a way to debounce the validation.
what does this mean? let's look at an example of typing "abcd"
regular form

1. type "a" - 3 renders
2. type "b" - 3 renders
3. type "c" - 3 renders
4. type "d" - 3 renders
total of 12 renders

with debounce validation

1. type "a" - 1 render
2. type "b" - 1 render
3. type "c" - 1 render
5. type "d" - 1 render
6. after 200ms 2 more renders for the validation part
total of 6 renders

let's say a typical input (eg. mail) will have 20 charachters. you can save about (60 vs 22) about 40 or third of the renders.

so in terms of UX. it's the best experience, as the user can type freely. and after 200ms from the "last" keystroke, it will validate the form.

https://gist.github.com/AlonMiz/e583946d3978de691ed53cece972e1a1

@MueezKhan246
Copy link

While performance is rarely an issue on forms, I've occasionally hit this issue on really big/complex forms with custom nested components.

The way I've gotten around it is making a form field component that tracks its own value state (setting only its own state onChange), then calls Formik's setFieldValue onBlur, instead of onChange. Then the re-render on a keystroke is contained to that field component rather than the whole form, while the value change is propagated to the rest of the form when the field loses focus.

how do you handle the errors and touch functionality provided by formik in this case, kindly guide?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests