formik: FastField does not avoid re-renders

šŸ› Bug report

Current Behavior

Using FastField components, changes on other fields end up re-rendering the whole form, including other FastFields.

Here’s a demonstration running the documentation example, where ā€œFirst Nameā€ is a FastField and ā€œEmailā€ is a regular Field.

  • Updates to ā€œFirst Nameā€ (FastField)

  • Updates to unrelated regular Field

Additionally, the code snippet in docs above does not compile as-is: it’s accessing an undeclared form variable in the render method of Formik.

Expected behavior

Changes on a Field should not trigger re-renders of unrelated FastFields.

Reproducible example

https://codesandbox.io/s/nrzq1vv1p (a simple, amended copy-paste of example from docs).

Your environment

Software Version(s)
Formik 1.4.3
React 16.7.0
TypeScript -
Browser Chrome 72.0.3626.96
npm/Yarn yarn 1.13.0
Operating System macOS 10.14.2 (18C54)

About this issue

  • Original URL
  • State: closed
  • Created 5 years ago
  • Reactions: 2
  • Comments: 16 (3 by maintainers)

Most upvoted comments

@jaredpalmer Why close this? We’ve shown there’s room for improvement here and FastField does not avoid all re-renders.

RenderFunction (I mean <Formik render={() => RenderFunction} />) always re-renders, I mean always when values, touched, errors etc. change. And we cannot memoize it (I mean wrap in React.memo). I have a hack for it: I make RenderFunction return another component (<AnotherComp {...props} />) which is wrapped in React.memo. That is how I prevent whole-form re-renders… Anyway maybe it works as it should.

Debounced HOC code for inputs.

I’ve been having problems with FastField, on Formik 2.1.4. Changes from other Fields will force rerenders of all FastFields. I made this HOC which takes a normal input (only tested with text or textarea) and wires it up to Formik in a debounced manner. Essentially, when the input is focused and the user is typing, it becomes an uncontrolled component, and every few seconds it syncs back up to the main formik value store by executing change handlers. When it’s not in focus, the input acts as a normal controlled component.

 * HOC that takes an input and gives it debounced onChange behavior.
 * 
 * This works by internally managing the value of the input, and every
 * so often communicating the updated value to formik via formik.handleChange
 * in a debounced manner.  This prevents lag on the form, because 
 * formik.handleChange will force a rerender of every input in the entire
 * form.  Normally this would be solved by Formik's built-in FastField, but
 * FastField is currently broken in Formik 2.x.
 * 
 * @param {*} WrappedInput input to debounce, must be TextArea or TextInput
 */
const debounceInput = (WrappedInput) => {
    return props => {
        const formik = useFormikContext();
        const [focused, setFocused] = useState(false);
        const [value, setValue] = useState(undefined);
        const debouncedHandleChange = useRef(_.debounce(formik.handleChange, 1000, {maxWait: 5000}));
        let newProps = {...props};
        const { name } = props;
    
        assign(newProps, {
            value: focused ? value : formik.values[name],
            onChange: e => {
                setValue(e.target.value);
                debouncedHandleChange.current(e)
            },
            onFocus: e => {
                setFocused(true);
                setValue(formik.values[name])
            },
            onBlur: e => {
                setFocused(false);
                formik.handleBlur(e);
            },
        })
    
        return <WrappedInput {...newProps}/>
    }
}

Same problem. I don’t get it…

Ok, follow up here.

After closer inspection, I realised that all re-renders were being caused by the connect HoC. Wrapping the component it returns in React.memo makes all why-did-you-update warnings go away.

@nfantone I’m just getting started with Formik and was under the same impression. But the highlight updates can be misleading since you have to consider the component tree.

After using the profiler, I think FastField is doing its job:

  • there seems to be a wrapper around FastField, and that wrapper is always rendered
  • but the FastFieldInner component is not re-rendered, even when we type into a regular Field (I typed a single character in the Lastname field) :
sandbox - codesandbox 2019-02-08 23-06-26 1

One thing that I’m not understanding is why do we get 4 commits in the profiler. It seems to be related to validation. Here’s the data for FirstName (FastField):

sandbox - codesandbox 2019-02-08 23-14-00 1 sandbox - codesandbox 2019-02-08 23-14-28 1 sandbox - codesandbox 2019-02-08 23-14-51 1 sandbox - codesandbox 2019-02-08 23-15-10 1

The isValidating prop changed twice, and that seems to be the reason of the 4 commits reported by the profiler.

Is it expected for the validation to run twice?

Thanks