Issue with values Formik
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.
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.
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