Source:  Twitter logo

GitHub discussion about this issue - https://github.com/jaredpalmer/formik/issues/529

I passed formik values to a function that is called in <Field/>'s onChange but when I type in the <Field/>, the last character does not fall into the function.

CodeSandbox - link

Screenshot:

Minimal Code Instance:

import React from "react";
import ReactDOM from "react-dom";
import { Formik, Form, Field, FieldArray } from "formik";

function App() {
  //------ HERE ----------
  const getValues = values => console.log(values.fields[0]);
  //----------------------
  return (
    <>
      <Formik
        initialValues={{ fields: [""] }}
        onSubmit={(values, actions) => {
          actions.setSubmitting(false);
          console.log(values);
          return false;
        }}
        render={({ setFieldValue, values }) => (
          <Form>
            <FieldArray
              name="fields"
              render={() => (
                <Field
                  type="text"
                  name="fields.0"
                  placeholder="Write something"
                  onChange={e => {
                    setFieldValue("fields.0", e.target.value);
                    //---------------
                    getValues(values);
                    //---------------
                  }}
                />
              )}
            />
          </Form>
        )}
      />
    </>
  );
}

ReactDOM.render(<App />, document.getElementById("root"));

This answer is based on Formik v1.5.7

You are trying to get stale value of values in your code right after calling setFieldValue which is internally doing async operation so you cant expect values being changed right after it (call) returns. Thus when you try to log value out of values you get state object during current (running) render cycle.

When the values do change, Formik re-renders the form in which it will have desired value that you are trying to get.

I moved up the code in your example to explain this.

Here's the updated code link.

Since this question is updated after my answer, I'll comment about this specific GitHub issue. The issue is in enhancement development request and solutions in the comments will not work in your example as values will still be "OLD" values.

Here's the reason why async await solution will not work in this case:

onChange={async e => {
    await setFieldValue("fields.0", e.target.value);
    getValues(values);
}}

Above code upon click event awaits on function called setFieldValue which gets executed and internally sets state which is async operation placed in the execution stack. Call returns and console.log logs values which happens to be existing render cycle values.

Hope that clarifies.

15 users liked answer #0dislike answer #015
Rikin profile pic
Rikin

I am not familiar with formik and I just took a quick look on the implementation. But here's my two cents on the issue.

Why are the values not updating after each call?

You are trying to get the values before the rerendering is happening and the value you have is the old one always, because the component is not updated yet.

To verify try to add console.log(values.fields[0]); in the App component before the getValue definition.

Why is the call happening before the rerendering?

The onChange implementation will trigger the handleChange in which it's a memoized function call that will not be triggered if you have the same value BUT you changed the value.

handleChange will be triggered if the executeChange value is different and executeChange also memoized that depends on setFieldValue or the state.values.

setFieldValue is an event callback that only update the ref after each render. That's from formik docs // we copy a ref to the callback scoped to the current state/props on each render and that didn't happen yet in your case -useEventCallback-.

Once the state is updated by the action then your component will be updated but your function is not called yet.

setFieldValue will try to validate the input and return a promise and bubble that up to executeChange and handleChange and it treats that as a low priority so it's not a blocking call.

A quick solution might be is to use onKeyUp instead of onChange I think that will bypass the useCallback and actually updates the component with higher priority call

function App({ values, setFieldValue }) {
  console.log("Rerendering",values.fields[0])
  //------ HERE ----------
  const getValues = () => console.log(values.fields[0]);
  //----------------------

  return (
    <div className="formik-wrapper">
      <Form>
        <FieldArray
          name="fields"
          render={() => (
            <Field
              type="text"
              name="fields.0"
              placeholder="Write something"
              onKeyUp={e => {
                setFieldValue("fields.0", e.target.value);
                getValues();
              }}
            />
          )}
        />
      </Form>
    </div>
  );
}

I hope this can help or at least lead for some help.

Check formik implementation

10 users liked answer #1dislike answer #110
Seder profile pic
Seder

If I understand your question correctly, you need a reliable way to get the latest state of Formik values, right after setting a Field's value & trigger another method/function/event based on this 'new' set of values. The obvious hindrance here is that setFieldValue() is an asynchronous method that internally uses setState() & hence there is no direct way to achieve this in a way you described / expected.

  • I wouldn't recommend the workarounds (setTimeout, random await just to bypass the nextTick, etc) mentioned in the github issue (https://github.com/jaredpalmer/formik/issues/529) since they are non-deterministic.
  • One way that worked for me is using componentDidUpdate (or any other comparable React Lifecycle method) and listening for change in the props.
  • Formik exposes the field values in React component's props at this.props.values
  • When you use componentDidUpdate, you just would need to compare the previous props to new props to check if something has changed.

Here is a minimal code to show what I mean.

const _ = require('lodash');

class Sample extends React.Component {
  componentDidUpdate(prevProps) {
    if(!_.isEqual(prevProps.values, this.props.values)) {
      // At least one of the Formik fields have changed. And this.props.values holds this newest data about of the fields.
      triggerWhateverWithNewValues(this.props.values);
    }
  }
}

Hope this helps.

7 users liked answer #2dislike answer #27
Royzer Williamraj profile pic
Royzer Williamraj

When you call getValues, it gets old value as setFieldValue Function call is not finished(so value is not set to Field.0). you can directly pass the Value to Function getValues though.

const getValues = valueField => console.log(valueField);


onChange={e => {
    setFieldValue("fields.0", e.target.value);
    getValues(e.target.value);
}}

https://codesandbox.io/s/spring-water-ixuuk

0 users liked answer #3dislike answer #30
Ravi profile pic
Ravi

I had a simmilar problem connected to setFieldValue and setFieldTouched. Before that I had function like this

onSelect={(value) => {
            setFieldTouched('vendor.id', true)
            policyTextAreaRef.current?.updateValue(value?.policy ?? '')
            setFieldValue('vendor', value)
            setFieldValue('policy', value?.policy ?? '')
          }}

But as it is described above I was always getting previous value not actual one . So workaround to that was making async function and adding await Promise.resolve() . Here is the working solution for me :

onSelect={async (value) => {
            setFieldTouched('vendor.id', true)
            await Promise.resolve()
            policyTextAreaRef.current?.updateValue(value?.policy ?? '')
            setFieldValue('vendor', value)
            setFieldValue('policy', value?.policy ?? '')
          }}
0 users liked answer #4dislike answer #40
andrej28 profile pic
andrej28

Copyright © 2022 QueryThreads

All content on Query Threads is licensed under the Creative Commons Attribution-ShareAlike 3.0 license (CC BY-SA 3.0).