Closed timoxley closed 6 years ago
You will always receive latest values
in render
prop. So you can perform anything you need there. I also recommend having a look at https://github.com/jaredpalmer/formik-persist how such functionality could be implemented. Take a note that <FormikPersist />
takes values from the context, you can of course pass it as prop instead.
@prichodko is exactly correct.
Sorry to reopen a closed thread, but this seemed like the most appropriate place to follow up on this.
If I understand correctly, the suggestion is to use another component to simulate onChange responses. I can see how that might make sense for something extremely agnostic like FormikPersist, but for the general situation where you wish to cause some effect based on form changes, this doesn't make sense to me.
Consider a simple form which contains dropdowns for the ingredients of a burger. You wish to render a preview of the burger outside the form somewhere. It would seem fairly simple (and conventional) to do this by passing the form an "onBurgerIngredientsChanged" handler, and hooking that up into the form's onChange handler.
Am I correct in understand you would suggest instead creating a <BurgerIngredientsChangedDetector />
component, complete with constructor, componentDidMount, componentWillReceiveProps methods ...
It seems to me this would very naturally lead people to creating their own <OnFormUpdate hander={someHandler}/>
component. In this case... why the extra complexity? Would it not be better to be able to pass Formik an onChange handler?
Just my two cents - I'm facing this problem now with a few forms that are slightly more dynamic than their siblings. Any thoughts would be much appreciated.
Thanks
Edit: Furthermore - and I think this is the bigger issue - by binding "onChange" type handlers into the render lifecycle, you're coupling two very different concepts. My form might render itself every second if it's being passed a readonly "counter" value - this doesn't mean any of the values have changed!
In case someone stumbles upon this thread, I've written a lib for this use case: https://github.com/ramitos/formik-observer
Hey,
I have been using Formik on 2 production projects now and I have needed this feature for both. At the moment I hijacked the validation method to trigger the onChange
. I feel like this would be worth considering as a built in feature?
You can't trigger side-effects in render() so this is required if you want to trigger any kind of side-effect. An onChange callback would just avoid user to have to build a special library for every single side-effect.
These side-effects could be:
Also notice how these usages are actually the only reason why one would use Redux-Form over Formik. Once you have that onChange callback, you can really easily save the state in Redux if you want to, which nullify the need of using Redux-Form and allow all the usages I said above.
See #401
Not something I'm proud of, but I hooked into the validate
prop to get access to the form values on every change 😏
// Rough sketch
class Component {
validate = values => {
// Do what you want with the values
console.log(values)
return {}
}
render() {
<Formik validate={this.validate} />
}
}
⚠️ I wouldn't recommend, but it might save some people that are in a pinch. Use my creative hacks at your own risk.
@ramitos - Great little package, perfect for what I needed it for. Thanks
Founded some interesting solution of this issue.
`
)}
`
@vsamotskiy What if I wanna update my component state in this.handleWholeFormChange(values)
?
I can't believe why I can't use properly and simply the onChange property.
Added a notifyOnChange
prop to formik. This should solve the form level onChange issue.
https://github.com/jaredpalmer/formik/pull/1397
PS: for now I have @brotzky 's creative hack in prod :)
Since notifyOnChange
hasn't been merged yet, I did this little hack for observing formik values.
import React, { useEffect } from 'react'
interface Props<T> {
onChange: (value: T) => void
value: T
}
export function FormikObserver<T>(props: Props<T>) {
useEffect(() => {
props.onChange(props.value)
}, [Object.values(props.value).join(', ')])
return null
}
FormikObserver.defaultProps = {
onChange: () => null,
}
<Form>
{(formProps: FormikProps<{}>) => {
return (
<Fragment>
// Your form
<FormikObserver value={formProps.values} onChange={value => console.log(value)} />
</Fragment>
)
}}
</Form>
What about doing something like this? Which I think is what @prichodko was saying in https://github.com/jaredpalmer/formik/issues/271#issuecomment-347142295
<Formik initialValues={initialValues} onSubmit={onSubmit}>
{({ values }) => (
<Form onChange={() => onFormChange(values)}>
{/* Input fields */}
</Form>
)}
</Formik>
I just ignore the synthetic event that's piped into the <Form />
onChange
event handler and replace it with the values dictionary from the <Formik />
value
render prop. Works like a charm in my project 😄
Didn't need any Observable third party implementations.
@ebrearley: it doesn't work for React Native as there is no <Form />
component there.
My use case: I need to verify isValid
after each change of all the pseudo-form's inputs and trigger an event outside Formik. In the render
method isValid
is not stable (!) The only place where it's stable is on the onChange event of one of the inputs. So the only solution for me was to listen for isValid
in each onChange
event of each input...
I think a better solution is more than needed. At least for React Native.
@dragosdehelean the proper way to handle something like this is to use an effect. An example of this implementation can be seen in:
I'm hoping to add some examples to the documentation in #1787. Let me know if this helps!
I don't agree that a separate component with an effect is the reasonable architecture. This is essentially diverting from the original (and reasonably sane) form configuration via props to configuration via XML.
You don't respond to the form submit
event by adding a component to your form:
<Form>
{(props) => (
<>
...
<SubmitObserver value={props.values} onSubmit={this.onSubmit} />
...
</>
)}
</Form>
Why would change
be different?
Logically both submit
and change
are simply one of many events that a form may emit, just like reset
or validate
.
I fail to see why different events should have so wildly different approaches in canonical use.
@Etheryte I absolutely agree with your argument that it's not intuitive. However, I do think a simple onChange wouldn't provide enough flexibility for all user scenarios, and if we don't find a way to provide for all user scenarios, there isn't a strong case to expand the API. Feel free to open a PR if you have a solution that can both optimize for renders when states are identical and allow users to circumvent this optimization if needed in a way that is clear to users. I haven't looked much into the v2 api so maybe there's a simple way now that we have hook support.
Edit: the "Form Submit Event" example doesn't really exist in React Native so it doesn't apply to the full audience of Formik.
Couldn't you just have two separate callbacks? onValuesChanged
and onNewValidValues
or something?
As this thread is still quite high up in the google search I post the unnecessary complex but new way here: https://formik.org/docs/api/useFormikContext
@valoricDe thanks, works perfectly
Guys, i found a better solution
<Formik>
{
(values) => {
useEffect(() => {
this.formOnChange(values);
}, [values]);
return (
<div>test</div>
)
}
}
</Formik>
@thushara5884 Using useEffect()
in a callback is not valid and is one of the classic examples of mistakes to avoid with hooks. If you check your browser console with the above example, you should find that you have an error warning you about this: "Invalid hook call. Hooks can only be called inside of the body of a function component."
.
@Etheryte yes you are correct. i was hoping formik internally uses children as another component, but it seems it use it as a callback function.
: children // children come last, always called
? isFunction(children)
? (children as (bag: FormikProps<Values>) => React.ReactNode)(
formikbag as FormikProps<Values>
)
<Formik>
{
(values) => {
const FormValuesListener = () => {
useEffect(() => {
this.handleFormOnChange(values);
}, [values]);
return null;
}
return (<Form>
<FormValuesListener />
</Form>)
}
}
</Formik>
This is the best solution i found so far.
What about doing something like this? Which I think is what @prichodko was saying in #271 (comment)
<Formik initialValues={initialValues} onSubmit={onSubmit}> {({ values }) => ( <Form onChange={() => onFormChange(values)}> {/* Input fields */} </Form> )} </Formik>
I just ignore the synthetic event that's piped into the
<Form />
onChange
event handler and replace it with the values dictionary from the<Formik />
value
render prop. Works like a charm in my project 😄Didn't need any Observable third party implementations.
I was ignoring this. I didn't knew how to get global events from <Formik>
component. Thanks @ebrearley 🎉 <Form>
component is huge of props and events ❤️
What about doing something like this? Which I think is what @prichodko was saying in #271 (comment)
<Formik initialValues={initialValues} onSubmit={onSubmit}> {({ values }) => ( <Form onChange={() => onFormChange(values)}> {/* Input fields */} </Form> )} </Formik>
I just ignore the synthetic event that's piped into the
<Form />
onChange
event handler and replace it with the values dictionary from the<Formik />
value
render prop. Works like a charm in my project smileDidn't need any Observable third party implementations.
Did anyone else find that this technique was always one change behind? In other words, the form's onchange is being called before formik has collected the change into the values object? In my case I need this for an autosave - luckily this was being debounced anyway so the delay wasn't an issue but I could see how it would be....
I don't understand their hesitancy in adding a context level onChange event. With Formik's approach being to collect and manage the form state at the highest level it seems fitting that it could report when the form is changing.
I have reviewed the approaches taken which boil down to:
1) Use a useEffect, and watch the values array for changes yourself. Not a great solution - I'm sure formik is already doing something like that internally. Adding another slow value comparison for large forms feels wrong 2) Use onChange on the form, and debounce the callback - introduce a delay to be sure values is fully up to date. If you need instance updates however you'll end up tuning that delay and getting race conditions on slow devices 3) Intercept onChange events from all of your form fields manually. The most 'correct' but completely unwieldly and opposite to the advantages of formik in the first place. It also doesn't work well with dynamic form generation or highly nested presentation layers.
Of course the issue here could be that formik internally is unable to easily tell when it's dispatch of new state to React has actually taken effect - so I'd expect it to have to monitor the values object itself - it just feels like it would be better for the library to do this...
@acuthbert it shouldn't be one render behind, by nature of effects being 1-to-1 with renders. it could be triggering your effect multiple times, like when validation is complete, etc, and so triggering
onChange(1)
(change updated to 2 before validation completes)
validation, onChange(1)
onChange(2)
Or your debounce fn could be using a stale callback. Formik doesn't have validation cancellation when the changes are stale, but unless validation is async it will always run in order. If you use async validation it's possible it would do something like onChange(1), onChange(2), validation(1) + onChange(1), validation(2) + onChange(2)
in v2. You should determine whether validation is causing an onChange and determine why (it shouldn't if useEffect dependencies are limited to values
). If you create a sandbox repro I can take a look.
Form
component wouldn't subscribe to state because it doesn't need any, but with onChange it would need to. The issue with onChange is, how do we build it in an extensible way that applies to everyone's situation? Just adding a hook dependency to values
seems like it would work for some and not others. Ultimately I think even if we implemented it, you'd want to create your own component that does the same thing for any advanced logic.Here's the de facto ChangeHandlingForm for v2. Notice it is impossible for values to become stale because the onChangeFn doesn't use values from outside the scope. If you pass values from outside the scope, it could become stale, so make sure those deps are passed to useEffect.
interface ChangeHandlingFormProps<Values> {
onChange: (values: Values) => void;
}
const ChangeHandlingForm = <Values,>(props: PropsWithChildren<ChangeHandlingFormProps<Values>>) => {
// in v3 this would be `const { values } = useFormikState<Values>();`
const { values } = useFormikContext<Values>();
useEffect(() => {
// you might modify this to fit your use case
// like adding a `const prevValues = usePrevious(values)` above and checking for equality
// or debounce this whole function but make sure not to use stale values
props.onChange?.(values);
}, [props.onChange, props.onChange && values]);
// Formik's Form component
return <Form>
{props.children}
</Form>
}
const MyForm = () => (
<Formik {...formikConfig}>
<ChangeHandlingForm onChange={values => console.log(values)} />
</Formik>
);
So using the validate()
hack results in validate being invoked multiple times. And the FormikObserver library looks interesting, but a google search shows me it has never been publicly used in combination with useFormik
Any ideas out there for me to implement a form-level onChange within useFormik()?
Edit: I ended up using a similar approach to the FormikObserver library, but hacking it together by hand:
validate: async (values: any) => {
// TODO: consider turning on loader, then delaying API call
// in order to reduce the number of API calls to backend
const stringified = JSON.stringify(values)
if (stringified !== stringifiedFormValues) {
setStringifiedFormValues(stringified);
console.log("running validate!!");
await debouncedUpdate(RequestType.Analyze, { values });
}
},
It seems that callin a function inside the render function with the values inside is always one step behind as mentioned.
Using the onChange method on <Form />
results in unexpected results.
So far the most solid way to accomplish that is creating a component that listens to changes according to the argument values of the render function and add a callback as prop, such as:
const FormikListener = ({ values, callback }) => {
useEffect(() => {
callback(values);
}, [callback, values]);
return null;
};
and inside the render function:
...
<FormikListener values={values} callback={handleOnChange} />
...
@MelMacaluso ended up doing the same... Values weren't updating on the internal Form
element's onChange
. Everytime it was 1 step behind. This seemed like the cleanest solution.
@MelMacaluso ended up doing the same... Values weren't updating on the internal
Form
element'sonChange
. Everytime it was 1 step behind. This seemed like the cleanest solution.
I think is very flaky and horrible but I had to get going, I wish I could hear take of the lib's creator so we do what is intended instead. @jaredpalmer 🙏🏻
<Formik> { (values) => { const FormValuesListener = () => { useEffect(() => { this.handleFormOnChange(values); }, [values]); return null; } return (<Form> <FormValuesListener /> </Form>) } } </Formik>
This is the best solution i found so far.
Ideally, onChange handler was expected to be already implemented - which wasn't case in 2023.
As a quick workaround, I brought in external state inside the form (which is very specific to use case I have). This is the least ugly, highly familiar and logical available solution that worked perfectly just in time.
indirect thanks to community, i was going through this and figured for my problem in svelte, it was showing up in google results, so for anyone looking to trigger on any child of form changed, just do this.
The <FormikLister />
solution is a nice one, but it has a caveat. Because functions are always different across renders (the callback
prop in this case) the useEffect
is going to get triggered everythime the parent re-renders.
const FormikListener = ({ values, callback }) => {
useEffect(() => {
callback(values);
}, [callback, values]); // <- dependencies always change because callback is always different
return null;
};
That can cause cause an infinite loop if, for example, the callback is doing some async logic and changing the state to show/hide a loader.
It can be easily fixed by wrapping the function passed to the callback
property in useCallback
, but it is easyly forgettable and can be a potential pitfall.
const handleOnChange = useCallback((values) => {
console.log(values)
}, []);
<FormikListener callback={handleOnChange}>
The <form>
approach does not work on mobile. I'm not sure why this is marked as closed, as it remains a significant pain point for all projects using Formik. I'm very tempted to switch to another form library at this point.
Nevermind, seems like you can use formik-effect, thank you :)
Ok, back again 😅 so formik-effect
didn't work, seems like the library is outdated, in the end I used a custom component for this:
export function FormikObserver<V>({ onChange } : FormikObserverProps<V>) : null {
const { values, initialValues } = useFormikContext<V>();
const previousValues = useRef(initialValues);
useEffect(() => {
if (!_.isEqual(previousValues.current, values)) {
previousValues.current = values;
onChange(values);
}
}, [values, initialValues, onChange]);
return null;
}
I have a form where some field's values depend on other fields values. I have been using redux-form and have been able to calculate these values using an
onChange
handler on the top-level form object.I don't see an equivalent using formik, is this right? I thought I could hijack the validation function to do this, but it's not passed the formik bag, only the values to validate.
Another usecase for a form-level
onChange
is automatic persistence without needing to hit a submit button e.g. something like:Another option could be a "submitOnChange` flag.