setFieldValue creates infinite loop

🐛 Bug report

Current Behavior

I have an input component that has some internal state (i.e. the inputs are made on another scale – e.g. values are written in millions instead of units. But the state of interest are always just the units.). This component only takes an initial value and not the current value as props. It also exposes a prop called onChangeValue which is basically a callback with the current value as input.

Expected behavior

The following should update formik.values.value but instead I get an infinite loop.

onChangeValue={value => formik.setFieldValue("value", value)}

Reproducible example

import { useFormik } from "formik";
import * as React from "react";

function CustomInput({ initialValue, scale, onChangeValue, name }) {
  const [value, setValue] = React.useState(initialValue / scale);

  React.useEffect(() => {
    onChangeValue(value * scale);
  }, [value, scale, onChangeValue]);

  return (
    <input value={value} onChange={(event) => setValue(event.target.value)} name={name} />
  );
}

export default function Demo() {
  const initialValue = 100;
  const formik = useFormik({
    initialValues: {
      value: initialValue
    },
    onSubmit: (values) => {
      console.log(JSON.stringify(values, null, 2));
    }
  });

  return (
    <form onSubmit={formik.handleSubmit}>
      <CustomInput
        initialValue={initialValue}
        scale={10}
        name="value"
        onChangeValue={value => formik.setFieldValue("value", value)}
      />
    </form>
  );
}

Solution without formik

The following solution works without using formik

import * as React from "react";

function CustomInput({ initialValue, scale, onChangeValue, name }) {
  const [value, setValue] = React.useState(initialValue / scale);

  React.useEffect(() => {
    onChangeValue(value * scale);
  }, [value, scale, onChangeValue]);

  return (
    <input
      value={value}
      onChange={(event) => setValue(event.target.value)}
      name={name}
    />
  );
}

export default function NoFormikDemo() {
  const initialValue = 100;
  const [value, setValue] = React.useState(initialValue);

  function handleSubmit(event) {
    event.preventDefault();
    console.log(value);
  }

  return (
    <form onSubmit={handleSubmit}>
      <CustomInput
        initialValue={initialValue}
        scale={10}
        onChangeValue={setValue}
      />
    </form>
  );
}

Your environment

Software Version(s)
Formik 2.2.1
React 16.14.0
TypeScript 4.0.3
Browser chrome
npm/Yarn npm
Operating System macOS

1 possible answer(s) on “setFieldValue creates infinite loop

  1. There’s a ton of related info here if you’re in for a read: #2268

    If this answers your question, we can close this as a duplicate of the above issue.