-
-
Notifications
You must be signed in to change notification settings - Fork 2.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
No onChange prop for <Formik/>? #1633
Comments
If you look at my original question, you'll see I'm not asking for an onChange on the inputs but rather, directly on the |
For those seeing this after the fact, here's what happened:
Hey, @buglessir what you just did is childish, despicable even. Thank you for your open source spirit. Keep at it, you're doing a great job. |
Maybe you can use |
I thought about that. I might fall into a loop: validate -> change data -> validate
|
You are talking about empty inputs (I belive all of them are input values for yours |
A simple case: I have a form with a list of inputs containing names. Every time a user changes any input, I'd like to have some kind of onChange prop (not the one on but higher) so that for example, I might add a new input for the user to input the next name. TL;DR: It's an autogrowing array of inputs for names.
|
So maybe just go to use onChange on
|
Is this something you used? I looked at the code. All that So, either I'm dumb, or your answer makes no sense. Does someone know if it's possible to do this? |
Yes I used it and it works... Just try... |
Just did. Made a codesandbox. I can confirm I'm dumb and it works. I just need to figure out why it does not on my project. Thanks |
Something like this <Formik
onChange={this.handleFormChange}
...
/> or withFormik const MyEnhancedForm = withFormik({
onChange: (name, value, { props }) => {
props.handleFormChange(name, value); // call some method from parent
},
...
})(MyForm); |
You can do this today with a formik effect I think. Not quite an ideal solution.
Usage
|
Hey, just run some method outside of Formik like this:
|
As I see |
@Unsfer the way I'd recommend to handle this is writing a |
It would be nice to have an onChange method to Formik with the latest form values. |
@AlonMiz I don't believe there is a "correct" way to detect onChange. For example, do we trigger on every change even if it results in the same value? Do we only trigger when the internal values are different? I think a user will want to configure it, and it should be opt-in only to begin with, so I think having a component inside of Formik which handles the changes works amazingly. I definitely saw a project which provides these handlers out of the box but my search-fu is failing me. I do think that Formik could provide these Effect components out of the box, but I think that would be a part of a separate issue. Edit: I don't fully agree with the API / naming conventions, but this is the package I was referring to: https://github.com/guiyep/formik-form-callbacks Note: I do not represent Formik, these are just my opinions. |
@johnrom From my perspective, it should be similar to the way |
That isn't quite how validate is triggered, nor is it how everyone who asks for the same functionality intends it to be triggered. Instead of creating a blanket case and getting 1000 issues about why The only other place I could recommend implementing something like this would be type FormikMessage<Values> =
| { type: 'SUBMIT_ATTEMPT' }
| { type: 'SUBMIT_FAILURE' }
| { type: 'SUBMIT_SUCCESS' }
| { type: 'SET_ISVALIDATING'; payload: boolean }
| { type: 'SET_ISSUBMITTING'; payload: boolean }
| { type: 'SET_VALUES'; payload: Values }
| { type: 'SET_FIELD_VALUE'; payload: { field: string; value?: any } }
| { type: 'SET_FIELD_TOUCHED'; payload: { field: string; value?: boolean } }
| { type: 'SET_FIELD_ERROR'; payload: { field: string; value?: string } }
| { type: 'SET_TOUCHED'; payload: FormikTouched<Values> }
| { type: 'SET_ERRORS'; payload: FormikErrors<Values> }
| { type: 'SET_STATUS'; payload: any }
| { type: 'SET_FORMIK_STATE'; payload: FormikState<Values> }
| { type: 'RESET_FORM'; payload: FormikState<Values> }; On every dispatch, the FormikMessage, original state and the resulting state could be passed to the callback. |
@johnrom I think implementing the <Formik
onDispatch={(message) => {
if(message.type === 'SET_VALUES' && payload.field === 'rate'){
formik.setFieldValue('total', payload.value * formik.values.qty)
}
return message
}
}>
...
</Formik> |
Building off https://github.com/guiyep/formik-form-callbacks this is what I'm currently using interface OnFieldChangeProps {
fields: string[];
onChange: (values: object) => void;
}
export const OnFieldChange = React.memo(({ onChange, fields }: OnFieldChangeProps) => {
const formik = useFormikContext();
const didMount = React.useRef(false);
React.useEffect(() => {
if (didMount.current) {
const values = fields.reduce((acc, fieldName) => {
acc[fieldName] = formik.getFieldMeta(fieldName)?.value
return acc
}, {})
onChange(values);
} else didMount.current = true;
}, [...fields.map(field => formik.getFieldMeta(field).value)]);
return null;
}); Usage <OnFieldChange fields={["rate", "qty"]} onChange={({rate, qty}) => {
formikBag.setFieldValue("total", rate * qty)
}}/> |
In case someone still interested (ts + Formik + MS Fabric UI for controls (but could be any other custom controls)) and control: return ( |
I implemented a simple solution using lodash, react hooks and kentcdodds/use-deep-compare-effect hook library. // FormikOnChange.js file
import { useRef } from 'react';
import PropTypes from 'prop-types';
import _ from 'lodash';
import useDeepCompareEffect from 'use-deep-compare-effect';
import { useFormikContext } from 'formik';
const FormikOnChange = ({ delay, onChange }) => {
const { values } = useFormikContext();
const isFirstRun = useRef(true);
const debouncedOnChange = useCallback(_.debounce(onChange, delay), []);
useDeepCompareEffect(() => {
if (isFirstRun.current) {
isFirstRun.current = false;
return;
}
if (delay > 0) {
debouncedOnChange(values);
} else {
onChange(values);
}
}, [values]);
return null;
};
FormikOnChange.propTypes = {
delay: PropTypes.number, // ms
onChange: PropTypes.func.isRequired,
};
FormikOnChange.defaultProps = {
delay: 0,
};
export default FormikOnChange; Usage: // import FormikOnChange from 'FormikOnChange';
<Formik>
{formikProps => (
<Form>
...
<FormikOnChange delay={500} onChange={values => console.log(values)} />
</Form>
)}
</Formik>
If you want to use this code as-is, you'll need to install the dependency hook first:
|
I just found a pretty easy solution to listen for changes: <Formik>
{(props) => {
const { values } = props;
useEffect(() => {
console.log('changed', values);
}, [values]);
}}
</Formik> . Maybe this has some drawbacks, but currently everything seems to be fine with it. |
Recently I also had to implement Formik based form with the change listeners, eventually, I used custom controls with controls from another library inside, not Field based, but using useField, for getting Formik helpers and value, in this case, you can use your own change handler provided via props from the parent component with form into a child field function component, the only difference - if such field component has a name property Formik will associate its value with its form object property e. g. { myField1: myField1Value, myField2: myField2Value, ... }. Otherwise, you have to use helpers from useField, like setValue and/or setTouched. Eventually, it's possible to integrate Formik into a quite complex form structure, with controls from any other library, the only condition - possibility to set onChange, onBlur event listeners in controls from this library. In my project, I used Fabric UI, where were almost no problems with this. |
@felixroos should work well with simple forms, but I see two drawbacks so far:
PS: haven't tested though. |
@felixroos how did you overcome |
@littlehome-eugene the render function of Formik is not a component. Therefore, you should create a component under it which uses useEffect: const MyOnChangeComponent = () => {
const { values } = useFormikContext();
useEffect(() => {
console.log('changed', values);
}, [values]);
return null;
}
const MyForm = () => (
<Formik {...initialValuesAndStuff}>
<MyOnChangeComponent />
</Formik>
) Please note the above will also occur when the values haven't changed after the initial mount. May not matter for your use case, though. |
This is how I am handling it FormikEffect component: import { useEffect } from 'react'
import { useFormikContext, FormikContextType } from 'formik'
export interface FormikEffectProps<T> {
onChange: (context: FormikContextType<T>) => void
}
export const FormikEffect = <T extends unknown>({ onChange }: FormikEffectProps<T>) => {
const context = useFormikContext<T>()
useEffect(() => {
onChange(context)
}, [context, onChange])
return null
} Usage: import React, { useCallback } from 'react'
import { Field, Formik, FormikContextType } from 'formik'
import { FormikEffect } from 'components/FormikEffect'
import { Input } from 'components/Form/Input'
import { noop } from 'utils/noop'
export interface MyFormValues {
foo?: string
}
const initialValues = {
foo: undefined
}
export const MyForm: React.FC = () => {
const handleChange = useCallback(
({ values, isValid, dirty }: FormikContextType<MyFormValues>) => {
if (isValid && dirty) {
// Do something with the form values
}
},
[]
)
return (
<Formik<MyFormValues> initialValues={initialValues} onSubmit={noop}>
{() => (
<>
<FormikEffect<MyFormValues> onChange={handleChange} />
<Field component={Input} name="foo" />
</>
})
</Formik>
)
} |
Thank you |
|
I found this library from Jared which may be more convenient for your use case, though I haven't tried it yet and I'm not sure it still works with the latest React. https://github.com/jaredpalmer/formik-effect |
There seem to be some rather complicated suggestons & ideas above about how to offer 'OnChange' at the Formik level in #1633. I'd like to propose what seems to me a simpler & potentially better approach. If you allowed a handler and passed it the causative event & a Formik "instance" as parameters, handling would be:
Simple example:
Or more concisely:
More complex example, with eg. conditions on the field & event:
Dependent field example -- note how logic is separate from the underlying fields, and composes with them. This is an architectural advantage of event handlers:
Currently, Formik makes it hard to access Formik's state from an event handler; and hard when creating Formik to be able to access the events it receives. My suggestion would solve this, by defining a handler that takes both as parameters. I believe a handler with access to the original Event and Formik's instance/ or state & helpers methods is not only simple, but near maximally flexible & customizable. I would advocate for considering this as a potential design approach in this much-requested feature area. Regards, Tom |
@twhitmorenz I don't think this solution is sufficient. If Formik accepted an onChange handler, I'd expect it to completely override what happens when an input is changed, as in reimplement the Formik The change handler would only have access to a FormikProps object that hasn't been updated with the latest asynchronous setFieldValue / setFieldTouched data. So you'd be subject to race conditions if you wanted to go any further than a single field dependency. This is the disadvantage of event handlers. const myHandleChange = (e, formik) => {
if (e.type === 'onchange' && e.target.name === 'city') {
// NOT Philadelphia, Illinois
if (
e.target.value === 'Philadelphia' &&
// this could be completely wrong, resulting in the wrong timezone
formik.values.state === 'PA'
) {
formik.setFieldValue('timezone', 'EST');
}
}
} The solution for this is to use what has been suggested throughout this issue: Effects as provided by React via useEffect which has a proper snapshot of a given state in time and will be eventually consistent with all values updates. My proposed solution would be passing a custom reducer to catch changes to formik.values as they happen at the reducer level. Alternatively, we could provide an API to get access to the latest Refs of |
Hello all, |
Thanks very much @johnrom for your thoughts. At a business level, I think generally people expect entering text/ selections into a form to enter text/ selections into a form. So the implicit Formik handling of these events (mark fields touched, gather the values) is pretty much an expected technical baseline.
I recognize you're raising an architectural issue here, as to whether it's generally desirable to use event handlers. I do see event handlers as the hugely common & well-understood programming model, so it's a tiny little bit unfortunate if Formik's current architecture is at a mismatch with that.
From a high-level perspective of maximizing simplicity:
Lastly John, some specific questions:
Thanks, |
@twhitmorenz this is a lot to respond to, but I'll break it into sections Replacing Formik Defaults, the onChange handler definitionI'm not arguing that the majority of developers will want to replace or lose the valuable Formik defaults. I'm arguing that it must be possible to override this functionality, or Formik won't work for some users who need to replace it. If this functionality can be overridden, it should follow naming conventions that make sense. In my mind, Formik.onChange would be a way to replace const formik = useFormik({
onChange: (event, formik) => {
if (event.target.name = "myField") {
// not possible in current API, basically setValues() with effects like setTouched and validate.
return formik.handleChange({
myField: event.target.value,
myOtherField: calculateOtherField(event.target.value),
});
}
return formik.handleChange(event);
}
});
useEffect(() => {
console.log('triggered when either (or both) fields are changed');
}, [myField, myOtherField])
return <FormikProvider formik={formik} />; Return false is an extremely outdated, non-functional event handler behavior. The behavior that might make sense here would be <input onChange={compose(firstAction, secondAction)} /> The API closest to DOM's "add as many event listeners as you want" while maintaining "functional" React-ness is a reducer dispatch. With a reducer dispatch, you can instead chain the reducer callback to do as many things as are necessary. Redux API is based around this concept. "monolithic antipattern given by Dependent Fields",It is React Hooks's core argument that many uses of the Events API are anti-patterns (they are side effects and not events), and that the Effects Hook is the correct pattern for achieving side effects in code. React event handlers and DOM event handlers do not act the same. Formik is based on the React event handler system. Handling dependent fields is an example of a cross-cutting concern. It doesn't always map cleanly to the responsibility of any one Component. Sometimes it maps closest to the reducer, sometimes a parent component of several different fields, sometimes the Form itself. But it's usually not a concern of a Field, because Field should not necessarily have knowledge of other Fields. Let me take away the oversimplified example you describe as monolithic, and speak directly to how this cross-cutting concern might be implemented with a better version of Formik's API using Effects. I'm ignoring what is and isn't Formik's current API because this is a demonstration of how it could work with just effects. const LocationFields = () => {
const { values: { zip }, valuesRef, setFieldValues } = useFormikContext();
const [isGeolocating, setIsGeolocating] = React.useState(false);
React.useEffect(() => {
// bypass http request if the user changed zips already
if (valuesRef.current.zip !== zip || !valuesRef.current.autoUpdate) {
return;
}
setIsGeolocating(true);
const geolocation = await geolocate(zip);
// check again, they still may have changed it
if (valuesRef.current.zip === zip && valuesRef.current.autoUpdate && geolocation) {
// doesn't exist in current api,
// set multiple fields' values then run touched and validations once
setFieldValues({
city: geolocation.city,
state: geolocation.state,
)};
}
setIsGeolocating(false);
}, [zip]);
return <>
<Field name="zip" />
<Field name="city" disabled={isGeolocating} />
<Field name="state" disabled={isGeolocating} />
<label><Field type="checkbox" name="autoUpdate" /> Auto update city and state?</label>
</>
}
class MyForm {
render() {
return <Formik initialValues={{
firstName: '',
lastName: '',
zip: '',
city: '',
state: '',
autoUpdateLocality: true,
}}>
<Form>
<Field name="firstName" />
<Field name="lastName" />
<LocationFields />
<button type="submit">Submit</button>
</Form>
</Formik>
}
} You can see that the above isn't a monolithic anti-pattern -- it's scoping the functionality to exactly the place that requires it, the cross-cutting concerns of only the fields involved. The above can also be done with class components as described below. But if it's an effect, it should probably use Class v Function componentsI'm not arguing for or against class or function components. However, you are unable to access Formik's context from a class component due to it using React hooks. All you need to do is wrap whatever you need in something that can access React hooks (a functional component). We didn't decide classes can't use React hooks, it is just how the API currently works.
class MyForm {
render() {
return <Formik>{(formik) {
this.handleRender(formik);
return <Field name="myField" />
}}</Formik>
}
handleRender(formik) {
// is this a good way to do things? not in my book
console.log('This will trigger anytime Formik's state is updated for any reason.');
if (formik.values.myField !== this.state.values.myField) {
console.log('value changed');
this.setState({ values: { ...this.state.values, myField: formik.values.myField } });
}
}
} Instead, wrap your class component in a functional component. class MyClassForm {
componentDidUpdate(prevProps) {
console.log('This will only trigger when props passed are not equal.');
if (prevProps.myField !== this.props.myField) {
console.log('value changed in componentDidUpdate');
}
}
render() {
<Form>
<Field name="myField />
</Form>
};
}
const MyWrappedForm = (props) => {
const formik = useFormik({
initialValues: { myField: '' },
onSubmit: () => {},
});
useEffect(() => console.log('value changed in effect'), [formik.values.myField]);
return <FormikProvider formik={formik}>
<MyClassForm myField={formik.values.myField} />
</FormikProvider>
} Or, you can create another class component under the Form which is passed the value and uses componentDidUpdate. class MyEffectLikeClassComponent {
componentDidUpdate(prevProps) {
console.log('This will only trigger when props passed are not equal.');
if (prevProps.myField !== this.props.myField) {
console.log('value changed in componentDidUpdate');
}
}
render() {
return null;
};
}
class MyForm {
render() {
return <Formik initialValues={ myField: '' }>
{formik =>
<Form>
<MyEffectLikeClassComponent myField={formik.values.myField} />
<Field name="myField">
</Form>
}
</Formik>
}
} |
You can also do this through the <Field name="fieldName" type="text">
{({ field: { onBlur, ...field } }) => (
<input
{...field}
onBlur={event => {
// Call the original onBlur
onBlur(event)
// Any custom behavior goes here
console.info(event.target.value)
}}
/>
)}
</Field> 💯 to @jaredpalmer for making an awesome library that's very extensible. |
uhm... i just add this to validation of fields which values state was needed How bad is this? :) |
I ran into a similar situation, where I needed to create some side effects when my form was in a particular state. Of all the solutions above, I gravitated towards the ideas presented by @felixroos and @johnrom:
My form is quite large and many of my fields are deeply nested into complex form sub-components. This makes it hard to have a single change handler fire when multiple fields are changed without prop-drilling down the handler function to the individual field components. Using the solution above works, but on large forms, it can cause the UI to lag significantly since the effect callback runs on every keystroke. My implementation of
The FWIW, I don't think that adding an |
@vojtechportes that's a nice general solution, just watch out that in your code you're also going to be invoking |
You should generally pass a ref'd, memoized or constant function as a value for callbacks (look at React docs for useEventCallback). Also yeah, I think I wrote a version somewhere that does all the checks for mounting, etc. In all, there should be a better system, but it would depend on my v3 PR landing. |
Hi folks, I don't know if this is still up to date but if this can help someone. I had to build an AddressBlock that can be standalone or mixed with an existing formik form so I had to be able to track onChange and update the parent Formik form. Here is a simple way I did it. const handleSave = async (value, formik) => {
if(standalone){
// Do the saving logic here.
console.log("saving it's own logic", value);
formik.setSubmitting(false);
}
}
const handleChange = async (e,value) => {
if(!standalone){
const input = e.target
const fieldValues = {
...value,
[input.name]: input.value
}
console.log("updating parent form", fieldValues);
handleParentChange(options.field_name,value);
}
}
return <Formik
enableReinitialize={true}
initialValues={values}
validationSchema={validationSchema}
onSubmit={(values, {setSubmitting, resetForm}) => {
setSubmitting(true);
handleSave(values, {resetForm, setSubmitting})
}}
>
{( formikProps) => {
const { handleSubmit, isSubmitting } = formikProps;
return (
<form onChange={ !standalone ? (e) => handleChange(e,formikProps.values) : false}>
<FormikInput type="hidden" value="id" formikProps={formikProps} />
<FormikInput type="hidden" value="type" formikProps={formikProps} />
<FormikInput type="text" value="line1" formikProps={formikProps} />
<FormikInput type="text" value="line2" formikProps={formikProps} />
<FormikInput type="text" value="city" formikProps={formikProps} />
<FormikInput type="text" value="zipcode" formikProps={formikProps} />
<BlockCountryRegion
data={{
country: formikProps.values.country,
region: formikProps.values.region,
}}
formikProps={formikProps}
/>
{standalone ?
<Button className="ms-4 float-end" variant="primary" onClick={handleSubmit}
disabled={isSubmitting}>
<ExoLabel id="form.save" />
<i className="ps-2 far fa-save"/>
</Button>
:''}
</form>
)
}
}
</Formik> Please notice the So depending if I want to be isolated I passe the "standalone" parameter in my component <AddressEditBlock
type={"main"}
address={values.primaryAddress}
standalone
options={{
field_name: 'primaryAddress',
from_table: 'organisations',
id: values.organisation_id,
}}
/> or I pas the onChange of the parent components <AddressEditBlock
type={"billing"}
address={values.billingAddress}
handleParentChange={handleChange}
options={{
field_name: 'billingAddress',
from_table: 'organisations',
id: values.organisation_id,
}}
/> Hope this help! Cheers. |
Hooks cannot be called inside callbacks. Ref |
@yasir-systemplus not sure what issue you're seeing, but you don't have to call a hook from useEffect. Formik provides an API which can be used within useEffect which will trigger a reducer to update which will update state and cause a new render, and hooks will be triggered during that new render. In #3231 , you can also access the current state via |
@johnrom Do you think FormikEffect solution still the best way to fire useEffects on specific prop changes or has anything changed since this thread was created? I'm having very similar issue, when I'm firing redux action when |
@priusGit unfortunately I would recommend moving away from this project, as it has not been maintained. My recommendations have not changed for the existing versions. |
❓Question
I've looked on the docs, I've looked on the issues and still have no clue about this.
The
<Formik/>
component has aonSubmit
prop but not anonChange
prop?What I want to do: Imagine an array of strings and everytime the users writes, I check of all the inputs are empty, I add a new one preventing him to click on "add new". Don't know if this is clear enough.
The text was updated successfully, but these errors were encountered: