I'm just playing with formik library and found performance issue with custom component in Field tag when the custom component uses styled components heavily.
When I keep pressing the key in an input box which is wrapped in multiple hierarchy of styled components, it causes huge lagging but instead of that, if I use simple divs, it is comparatively faster.
@mjangir We are having the same issues. Using debounce to handle this.
I may not have as many styles as you do, but I do have all custom components. I saw this in another thread and it turned out to be true (runs as normal/faster in production)
https://github.com/jaredpalmer/formik/issues/671#issuecomment-424792264
I'm seeing the same issue with one input in a form which is a styled component - pressing and holding any key causes a large and noticeable lag
Incidentally, I've tried using <FastField /> and it hasn't made any difference.
@joycollector, where are you placing the debounce?
I am having this issue as well. I tried <FastField /> with no success either.
I was thinking I may have to use vanilla CSS with Formik to fix this bug.
I've had similar major performance issues with relatively short forms: a custom drop down and 4-5 custom input fields. I'm using tailwind.js (similar to styled components) and Yup for validation.
Same issue here, I can confirm that production is not affected. It lags only in dev mode.
That’s interesting. Can you / someone reproduce this behavior in a sandbox?
I haven't check, but trying around a bit I think that production is affected too, but way less than dev.
BTW I solved my problem setting the form value from onBlur instead of onChange.
Same issue here, production is effected - but way less
same but fastfield helped and in prod it's acceptable
same issue.
This should be fixed with 1.4.0. https://github.com/jaredpalmer/formik/releases/tag/v1.4.0
Thanks!
On Sat, Dec 8, 2018, 6:05 PM Jared Palmer <[email protected] wrote:
This should be fixed with 1.4.0.
https://github.com/jaredpalmer/formik/releases/tag/v1.4.0—
You are receiving this because you commented.
Reply to this email directly, view it on GitHub
https://github.com/jaredpalmer/formik/issues/1026#issuecomment-445496621,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AAUMymNcVbyIH279-kv0xp7TJ2TU8EFiks5u3EXTgaJpZM4XxuvD
.
Hmm, not seeing any improvement on 1.4.0 for me. A single key press is causing 2 renders:

This is the code for the form:
import React, { PureComponent } from 'react';
import { css } from 'emotion';
import { Formik, Form } from 'formik';
import * as Yup from 'yup';
import { Flex, Button, Input, InputGroup } from '@livery/Components';
const FlexForm = css`
display: flex;
flex-direction: column;
width: 60%;
padding: 2rem 0;
`;
const schema = Yup.object().shape({
display_name: Yup.string()
.min(2, 'Display name is too Short!')
.max(50, 'Display name is too Long!')
.required('Required'),
statement_descriptor: Yup.string()
.min(2, 'Too Short!')
.max(50, 'Too Long!')
.required('Required'),
});
export default class StripeProductEditForm extends PureComponent {
constructor(props) {
super(props);
this.state = {
initialValues: {
display_name:
(props.formData && props.formData.stripe_product.display_name) || '',
statement_descriptor:
(props.formData &&
props.formData.stripe_product.statement_descriptor) ||
'',
active:
(props.formData && props.formData.stripe_product.active) || false,
},
};
}
render() {
return (
<Flex column align="center">
<Formik
initialValues={this.state.initialValues}
validationSchema={schema}
onSubmit={this.props.onSubmit}
render={({ values, errors, touched, handleChange, handleBlur }) => (
<Form className={FlexForm}>
<InputGroup>
<Input
name="display_name"
label="Display Name"
value={values.display_name}
onBlur={handleBlur}
onChange={handleChange}
type="text"
errorMessage={
errors.display_name &&
touched.display_name &&
errors.display_name
}
my={2}
py={3}
px={3}
/>
<Input
name="statement_descriptor"
label="Statement Descriptor"
value={values.statement_descriptor}
onBlur={handleBlur}
onChange={handleChange}
type="text"
errorMessage={
errors.statement_descriptor &&
touched.statement_descriptor &&
errors.statement_descriptor
}
my={2}
py={3}
px={3}
/>
</InputGroup>
<InputGroup mt={3}>
<Flex justify="space-between">
{this.props.isEditing && (
<React.Fragment>
<Button
error
mr={'auto'}
py={3}
px={4}
onClick={() => {
this.props.onDestroy(
this.props.formData.stripe_product.id,
);
}}
>
Delete Product
</Button>
{!this.props.formData.stripe_product.active && (
<Button
ml={'auto'}
mr={'auto'}
onClick={() => {
this.props.onActivate(
this.props.formData.stripe_product.id,
);
}}
>
Activate Product
</Button>
)}
<Button
ml={'auto'}
mr={'auto'}
onClick={() => {
this.props.onSync(
this.props.formData.stripe_product.id,
);
}}
>
Sync Product
</Button>
</React.Fragment>
)}
<Button
success
ml={'auto'}
py={3}
px={4}
name="create"
type="submit"
>
{this.props.isEditing ? 'Update' : 'Create'} Product
</Button>
</Flex>
</InputGroup>
</Form>
)}
/>
</Flex>
);
}
}
I appreciate this is not very helpful without a repro, will try and get one up and running this weekend.
@jaredpalmer Awesome! So I just upgraded to 1.4.1 and the good news is that the double re-render for a single key press is fixed, and the form input speed is now acceptable on small forms (3 or less inputs).
The bad news is that on forms with 5 or more inputs, the performance issue remains (typing is slow, pressing and holding a key freezes the UI). Will investigate more.
thanks @johnmcdowall I have same issue :(
I'm also still experiencing the same issues after upgrading to 1.4.1.
In my case the unnecessary re-renders are really limited to the components using styled-components. But, it only effects styled-components that are within the scope of the formik's <Formik /> component.
After replacing all styled-components with simple div's within the form the issue was resolved and only affected fields were re rendering like expected.
Hi @jaredpalmer for me there is a marked improvement in 1.4.1, so thank you for that. I went from 3 renders to 2, which I suppose made it 33% faster. Overall, I think the onus is on styled components more than on you.
@baleeds Are you using styled-components in your Formik forms? Because this performance issue didn't happen for me when using other React form libraries, or just standard forms, so there would seem to be a Formik specific issue here...
Apparently styled-components are always rerendering if the parent component triggers a rerender (See https://github.com/styled-components/styled-components/issues/1723). In my case, I have quite a lot of styled components inside the form, which are all being rerendered as soon as a value changes, since they're stored in the form's state.
So the solution probably lies in the use of wrapper components (e.g. PureComponent's) around the parts that use styled components to avoid rerenders manually.
@johnmcdowall we are using styled components throughout the whole app, which includes inside of formik forms. My observation is that styled components are slow because they cause things that don't need to be components to be components, which means more render functions run (in our case, WAY more, since almost all html elements are SC).
This is compounded by the multiple renders on each keystroke of formik. Anything that causes multiple renders would multiply the issue.
@baleeds is correct. styled-components also hooks every single component into context for theming...slowing down things even more.
@jaredpalmer Just an update: after updating a bunch of libs and tweaking my base style class things seem to be pretty rocking now, no noticeable lags for me anymore 👍
@johnmcdowall @jaredpalmer Hello guys, I keep facing the same problem with performance, using Styled Components and Formik. May you share the solution please if there are any? Like what libs to update etc.? I tried <FastField />, but it didn't help, and with single React and manually configured shouldComponentUpdate everything works fast enough. Maybe there are some way to do the same but with Formik? Any help very appreciated. Thanks!
@johnmcdowall Hey! could you be a little more descriptive as on which libs you updated and which tweaks you made? I am also experiencing this problem...
@oleksii-korenev @bpallares have you guys tried the React profiler to see where the render time is being spent? For me it wasn't about changing formik, it was about reducing overall render time by removing styled components from heavily repeated elements, which can be identified in the profile. This will increase ALL render times, including Formik's.
@oleksii-korenev @bpallares Yeah but what worked for me, worked for my app and my app is not your app. I'm using emotion for one thing. And a custom base style entity that I was injecting in. When I spoke about updating libs I just meant in general, not that one specific update fixed everything.
Sorry but this is the kind of thing you'll need to debug for your circumstance 🤷🏼♂️
@baleeds Yeah exactly, I used the profile as well to narrow down that my base style entity was causing too many re-renders (along with the previous Formik [now fixed] multiple re-renders).
Maybe this helps a bit meanwhile? https://travix.io/using-recompose-hocs-to-get-better-performance-in-forms-using-formik-and-yup-e51024d645ba
Fastfield did not work for me too. Form has ~20 fields and the lag was noticeable.
This is how I solved it [[link to gist](https://gist.github.com/rohanBagchi/ed5841d65f12c259b13c780ee308c83e)]
import React, { useState, useEffect } from "react";
import toString from 'lodash/toString';
interface ChildrenPropType {
handleChange: (e: any) => void,
value: string
}
interface PropType {
fieldData: any,
getFieldUniqueId: (data: any) => string,
getFieldValue: (data: any) => string,
children: (data: ChildrenPropType) => JSX.Element
}
const Inner = (props: PropType) => {
const [inner, setInner] = useState("");
const [existingItemId, setExistingItemId] = useState("");
useEffect(() => {
const {
fieldData,
getFieldValue,
getFieldUniqueId
} = props;
if (!fieldData) return;
const initialValue = getFieldValue(fieldData) || "";
const fieldUniqueId: string = toString(getFieldUniqueId(fieldData));
if (!existingItemId || !existingItemId.length || existingItemId !== fieldUniqueId) {
setInner(initialValue);
setExistingItemId(fieldUniqueId);
}
});
const handleChange = (e: any) => setInner(e.target.value);
return props.children({ handleChange, value: inner })
};
export default Inner;
Now to use it, [[link to gist](https://gist.github.com/rohanBagchi/5d3fa1410a012f1e34f51671bdeb5856)]
const innerFieldData = {
id: props.selectedItem.id,
...values, // formik's form field values
};
const getFieldUniqueId = (data: any) => data.id;
<InnerField
fieldData={innerFieldData}
getFieldValue={(innerFieldData: any) => innerFieldData['itemDescription']}
getFieldUniqueId={getFieldUniqueId}
>
{({ handleChange: innerHandleChange, value }: any) => {
return (
<TextArea
placeholder='DESCRIPTION'
name="itemDescription"
value={value}
onBlur={(e: any) => {
handleBlur(e); // formik's handleBlur
handleChange(e); // formik's handleChange
}}
onChange={innerHandleChange}
/>
)
}}
</InnerField>
That way sync happens only when user blurs and the field maintains it's state internally while user is typing.
I managed to achieve the same result as @rohanBagchi , I've been experimenting with the next v2.0.1-rc13 hooks. Instead of useField, I've created a custom hook that was more performant with the cost of a slightly different behaviour (the values are updated on blur, however, the error state isn't resolved on change, but on blur)
export function useFastField(props) {
const [field, meta] = useField(props);
const [value, setValue] = useState(field.value);
const { onBlur, onChange } = field;
field.value = value;
field.onChange = (e) => {
if (e && e.currentTarget) {
setValue(e.currentTarget.value);
}
};
field.onBlur = (e) => {
onChange(e);
onBlur(e);
};
return [field, meta];
}
With this, you can spread the fields and properties just like you do in useField
const MyField = () => {
const [field, meta] = useFastField(props);
return (
<input {...props} {...field} />
);
};
I noticed that on every key stroke two values always change:
errors an array of unchanged valuesgetFieldMeta which is a functionI used Jakob Rask's trace code:
``
function useTraceUpdate(label, props) {
const prev = React.useRef(props)
React.useEffect(() => {
const changedProps = Object.entries(props).reduce((ps, [k, v]) => {
if (prev.current[k] !== v) {
console.log('Changed: ' + k)
ps[k] = [prev.current[k], v]
}
return ps
}, {})
if (Object.keys(changedProps).length > 0) {
console.log(Changed props in ${label}:`, changedProps)
}
prev.current = props
})
}
@mblarsen I am reading Formik's code, it seems that getFieldMeta is recomputed each time errors changes, so it should be enough to avoid updating errors.
Looking at:
https://github.com/jaredpalmer/formik/blob/master/packages/formik/src/Formik.tsx
I see that state.errors could be recomputed in the main reducer for the case 'SET_FIELD_ERROR'.
case 'SET_FIELD_ERROR':
return {
...state,
errors: setIn(state.errors, msg.payload.field, msg.payload.value),
};
Could it be that setIn isn't correctly returning the original state.errors if no field was updated?
Could it be that
setInisn't correctly returning the originalstate.errorsif no field was updated?
That would surely explain the inequality of the errors, despite being unchanged.
Same issue for my form with over 1K fields.
The method OnChange seems to be the problem, so I only trigger the formik onChange onBlur like Rohan's suggestion.
const SimpleField = (props: SimpleFieldProps) => {
const { index, field, formik } = props;
const value = formik.values["clients"][index][field];
const [fieldValue, setFieldValue] = useState(value);
return useMemo(() => {
return (
<input
type="text"
className={"MuiInputFast-input"}
id={`clients.${index}["${field}"]`}
name={`clients.${index}["${field}"]`}
onChange={(e: any) => setFieldValue(e.target.value)}
onBlur={(e: any) => {
formik.handleBlur(e);
formik.handleChange(e);
}}
value={fieldValue}
/>
)
}, [field, index, fieldValue]);
}
Same issue here!, I just have only five fields, if remove handleBlur trigger, the performance change is several.
Try https://react-hook-form.com/ easier to use and no more lag :)
Try https://react-hook-form.com/ easier to use and no more lag :)
Thanks!!! A lot!!
A few things I have noticed that cause a slow form, and how to fix them...
If you are using react-create-app you will probably have
If you have select fields, they will render the whole list on every update, keypress or whatever. You will need to cache / memorise values / components with long lists.
Production builds do work much faster without React's internal development logging, debugging etc...
You could separate Formik field props from the input / select components and memorise the input / select. This works for MaterialUI components and probably others as well.
const InputPrimitive: React.FunctionComponent<InputPrimitiveProps> = (props) => {
//your expensive code goes here
return (
<div>
<input {...props} />
</div>
);
};
const MemorisedInputPrimitive = React.memo(InputPrimitive);
const Input: React.FunctionComponent<InputProps> = (props) => {
const [field] = useField(props.name);
return <MemorisedInputPrimitive {...props} {...field} />;
};
The above stops all inputs but the one you are typing into re-rendering. Formik still performs all its calculations on every keypress, change, blur etc... but only the component being manipulated is rendered. Other on the page do not.
@mattsputnikdigital is spot on with the above solutions.
In general, any CSS-in-JS library that does runtime style calculations are slow. To add insult to injury, many CSS-in-JS solutions require you to use a <ThemeProvider> from which all components rely on--or a <Box> component primitive. The problem is that there is a ~2x performance penalty during render for each component that hooks into context. Multiply this by hundreds of components and it's death by 1000 cuts.
For a more detailed analysis of the issue, see this article: https://calendar.perfplanet.com/2019/the-unseen-performance-costs-of-css-in-js-in-react-apps/
So what can be done:
<Formik><Form>...</Form></Formik> to avoid the render prop inline function running on each keystroke.map() of large lists
Most helpful comment
@baleeds is correct. styled-components also hooks every single component into context for theming...slowing down things even more.