Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Issue with values Formik

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:

enter image description here

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"));
like image 534
Arthur Avatar asked Jun 15 '19 19:06

Arthur


People also ask

How do I access values in Formik?

it is very simple just do console. log(formik. values) and you will get all the values without submitting it.

How do you set value in Formik field?

You don't need to use value={values.name} you can use Formik props like onSubmit to get your values. Also what version are you using? please note that render method has been deprecated.

Does Formik need initial values?

Initial field values of the form, Formik will make these values available to render methods component as values . Even if your form is empty by default, you must initialize all fields with initial values otherwise React will throw an error saying that you have changed an input from uncontrolled to controlled.


3 Answers

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.

like image 197
Rikin Avatar answered Oct 18 '22 08:10

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

like image 20
Seder Avatar answered Oct 18 '22 08:10

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.

like image 31
Royzer Williamraj Avatar answered Oct 18 '22 09:10

Royzer Williamraj