Formik: How to avoid rerendering?

Created on 12 Jan 2018  ·  68Comments  ·  Source: formium/formik

Question

Hello!

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

Duplicate Enhancement in progress

Most helpful comment

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?

All 68 comments

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?

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

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.

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

@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 :)

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>
    )
  }
}

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

@jaredpalmer can use it in react native?

@jaredpalmer Thanks it's working smoothly now

Not yet. Let me make an issue for that.

npm i formik@next

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?

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.

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",

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

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...

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

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?

I think so

I'm having this problem too..

Same here

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

I've been looking at the runValidations function: https://github.com/jaredpalmer/formik/blob/a35fba4951992e7af8eb37cbf73dac83e2817fe7/src/Formik.tsx#L265-L285

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...

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.

@noinkling I'm seeing the same thing. The change to state from isValidating being turned on/off is causing a ton of re-rendering.

This was a huge issue with using redux because it was causing renders on every input, some renders took 50-100ms.

My workaround for this based on reading this thread is to set validationOnChange=true and validationOnBlur=false then create a component that manages its own state and fires field.onChange when onBlur is fired

class WorkaroundInput extends Component {
  constructor(props) {
    super(props)
    this.state = {
      value: props.value,
    }
    this.onChange = this.handleChange.bind(this)
  }
  handleChange(e) {
    this.setState({ value: e.target.value })
  }
  render() {
    return (
      <input
        {...this.props}
        {...this.state}
        onChange={this.onChange}
        onBlur={this.props.onChange} // only change on blur
        value={this.state.value}
      />
    )
  }
}

Same here...

@jmcgonegal Your solution definitely sped things up but doesn't update the Formik state if I press the Enter key to submit the form. This has been working for me (using Material-UI):

edit - I noticed that the original solution didn't work with form filling extensions (i.e. LastPass) either. I can't find any way to support this without updating the Formik state when the field changes, so I've added a debounce.

export class TextFieldBase extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      value: props.value,
    };

    this.syncChange = debounce(this.syncChange, 200);
  }

  syncChange = (e) => {
    const { onChange } = this.props;
    onChange(e);
  };

  handleChange = (e) => {
    if (e.persist) {
      e.persist();
    }

    this.setState({ value: e.target.value });
    this.syncChange(e);
  };

  handleKeyDown = (e) => {
    const { onChange } = this.props;

    if (e.which === 13) { // enter key
      onChange(e);
    }

    return true;
  };

  render() {
    const { value } = this.state;
    const { onChange } = this.props;

    return (
      <MuiTextField
        {...this.props}
        {...this.state}
        onChange={this.handleChange}
        onBlur={onChange}
        onKeyDown={this.handleKeyDown}
        value={value}
      />
    );
  }
}

export const TextField = ({
  name,
  ...restProps
}) => (
  <FastField
    name={name}
    render={props => (
      <TextFieldBase {...fieldToTextField(props)} {...restProps} />
    )}
  />
);

Note that fieldToTextField comes from formik-material-ui (for those looking to get material-ui working)

same here...

We also tried make a 'fast input' which only updated on blur, but the side-effects were too much, though the debounce solution seems better. Now we use that 'fast' component when necessary, but mostly just use regular onChange inputs.

My vote would be to work on @noinkling 's point about setting isValidating. We aren't using that prop, so I imagine that could save us 1 render, and doing a deep comparison of errors would be useful too, but maybe both of those could be parameters on withFormik (useIsValidating, deepCompareErrors)?

I'm also Having BIG performance issues on development builds, prod builds works a lot faster but still less than ideal.

Another idea is to move every Field/input to separate Component/PureComponent. Yes, it takes time to develop forms this way, but React is not re-render other components except changed one in this case.

Validation on every input change can be debounced.

If y'all haven't already, try the React devtools profiler. We found out that using styled components for our list items was causing large render times, which was multiplied by the formik re-renders. Probably cut our render time in half.

The problem is isValidating and uncancelled promises. This should be fixed in 1.4 which I hope to cut tomorrow

I was experiencing quite serious slowness using <Formik /> with 1.4.0 despite having a rather simple form (an input and a radio group) which contained schemaValidation using yup.

In my case, the validation function was doing heavy computation which was causing the lag on input change. Once I've moved moved that part outside the validation function, things began working pretty fast.

There's still _some_ lag, and the components seem to render twice in a row on every keystroke, but performance seems acceptable.

Sorry for hijacking, but any ideas what would cause a double-render of components inside <Formik />/<Form />? In fact, some more complex input components re-render 4 times.

Issue: https://github.com/jaredpalmer/formik/issues/1187

Looks like the two causes for unnecessary rerendering that I mentioned in my previous comment are both addressed as of 1.4.1 (not in a customizable way, but at least in a way that makes sense for the vast majority use case) :+1:

If y'all haven't already, try the React devtools profiler. We found out that using styled components for our list items was causing large render times, which was multiplied by the formik re-renders. Probably cut our render time in half.

@baleeds What did you change with your styled components implementation to cut render time? Simply remove it?

@maciej-gurban Are you considering that touched and blur on an input can cause re renders? Just an idea.

@Nemsae yeah I just removed them in cases where they were rendered more than 10 or so times. List items and things. If I could go back, I don't think I'd use styled components, tbh. After looking at the profiler, the performance doesn't seem worth the features.

is there a solution other than the @alexplumb's one?

I found a workaround for that:
1) Turn off form-level validation (except for submit)
<Formik validationSchema={validationSchema} validateOnChange={false} validateOnBlur={false} ...
2) And add custom validation in onChange handler by using yup's reach function
```
name={name}
render={({ field, form }) => (
{...field}
onChange={e => {
Yup.reach(validationSchema, field.name)
.validate(e.target.value)
.then(() => form.setFieldError(field.name, undefined))
.catch(e => form.setFieldError(field.name, e.message));
form.handleChange(e);
}}
/>
)}
/>
````

After struggling with this for so long, @jmcgonegal comes in the clutch. Thank you!!!

Please take a look at using-recompose-hocs-to-get-better-performance-in-forms-using-formik-and-yup. Basically, using shouldComponentUpdate to decide if an input should re-render, is the best approach.

A was struggling with a form that contains many pickers (Using apollo+graphql+react-native) and this just works like a charm!.
If you are using Functional Components, this could help:

const FormInput: FunctionComponent<InputProps> = ({
  label,
  value,
  autoCapitalize,
  autoCorrect,
  onChangeText,
  onBlur,
  secureTextEntry
}) => {
 // some code....
  return (
    <Input
      inputContainerStyle={{ marginBottom: 5 }}
      label={label}
      onChangeText={onChangeText}
      value={value}
      autoCapitalize={autoCapitalize}
      autoCorrect={autoCorrect}
      onBlur={onBlur}
      secureTextEntry={secureTextEntry}
    />
  )
}

function areEqual(prevProps: InputProps, nextProps: InputProps) {
  return prevProps.value === nextProps.value
}

const SignInFormInput = memo(FormInput, areEqual)

This was a huge issue with using redux because it was causing renders on every input, some renders took 50-100ms.

My workaround for this based on reading this thread is to set validationOnChange=true and validationOnBlur=false then create a component that manages its own state and fires field.onChange when onBlur is fired

class WorkaroundInput extends Component {
  constructor(props) {
    super(props)
    this.state = {
      value: props.value,
    }
    this.onChange = this.handleChange.bind(this)
  }
  handleChange(e) {
    this.setState({ value: e.target.value })
  }
  render() {
    return (
      <input
        {...this.props}
        {...this.state}
        onChange={this.onChange}
        onBlur={this.props.onChange} // only change on blur
        value={this.state.value}
      />
    )
  }
}

Regarding custom Inputs and such, I am doing the below to get a good performance:

A good approach is to simply use recompose mapProps, and shouldUpdateForKeys ... and just map what you need from formik ...

Something else that is good is to use the connect feature only deep inside the component that needs to access anything from formik, so you leave your app not re-rendering due to formik values changes on each keystroke.

If you have to foreach a formik property, I would suggest to foreach a ghost and then access the property using the id directly.

Was this fixed?

I'm having a similar problem with a form with around 6 fields.
Selecting an input and pressing and holding a key makes every input to re-render and very very sluggish performance wise.

@JoaoFGuiomar ... basically the way to solve it, is to not use Formik.

Unfortunately, I had to spend the entire weekend building a new form library to solve the problems a few months ago. I used redux to solve the problem ... Maybe you can try the redux form library available out there.

The problem with Formik is simple to understand: they put all the data inside a "context", this means, everything will re-render on every single input key press. Or if a field is touched, or if anything happens to the form.

To solve this, one, do not use formik, two, you can use useMemo around your JSX. But that takes a some work, I dont know how you are using it ...

```
const Component = ()=> {
return useMemo(() => (

), [formik.values.whateverValueyouwant)
}

@lucastschmidt Thanks for the reply! Will try the useMemo solution.

Cheers!

@lucastschmidt A shame really. Was in the middle of researching using the library for one big form on a website mend for handling multiple users with many fields.
Formik seems to check all the requirements I've had...except for the re-rendering issue, which currently is a deal breaker.

Posting this for more of a +1 on the whole re rendering problem.

@AlanKrygowski I think there are some other ways, like if you use their fast fields ... but, looking at the implementation, if everything is available on the context side, as a single context, it will cause re-render ... only memos can save.

If you really stop and think about it, it is extremely difficult to do a form that has all the data centralised without causing a re-render. You need some sort of redux technology. I would be curious to know if there is a way to architect the library in a way that doesnt create this problem. I just went with redux since I had to solve it quick.

Wanted to share my experience with the re-rendering issue.
We have a form with ~30 fields, some of them are bulky components like react-select and a date picker, those appear multiple times in the same form. When debugging it was impossible to type anything on any field, in production it worked OK, we needed to optimize fast so we started to by using the FastField and dividing the form in two pages (steps), the second one with non-required fields.
We are always checking for new libraries that solve this issue but the reality is that from an architectural stand point is something pretty hard to do and almost any library carries with the same problems, right now we are testing React Final Form and it seems pretty promessing.

Just to share with you guys, I've switched to react-final-form long time ago when had multiple issues with Formik. You can control when to re-render field or form using subscriptions there.

Give it a shot, maybe it will solve your issues too 😉

Another way is to create an "inner" component with the implementation of actual field, for example a react-select. The "outer" component has all the field and form properties from formik and actually rerenders in every change, here lives the setFieldValue, the state, etc. The inner component has limited props, only what is needed for its core operation. With this way the inner component rerenders under control. I created a really big form with 20+ inputs and 10+ react-select fields some months ago with no rerender problem. I have to search my code to share an example.

@everk can u share the code via codesandbox or any mean, it will be Very helpful

Thanks

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

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

@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.

@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.

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.

@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.

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

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! :)

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

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?

@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

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.

@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.

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.

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.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

green-pickle picture green-pickle  ·  3Comments

pmonty picture pmonty  ·  3Comments

ancashoria picture ancashoria  ·  3Comments

jordantrainor picture jordantrainor  ·  3Comments

Jungwoo-An picture Jungwoo-An  ·  3Comments