Skip to content
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

Open
mintyPT opened this issue Jun 23, 2019 · 48 comments
Open

No onChange prop for <Formik/>? #1633

mintyPT opened this issue Jun 23, 2019 · 48 comments

Comments

@mintyPT
Copy link

mintyPT commented Jun 23, 2019

❓Question

I've looked on the docs, I've looked on the issues and still have no clue about this.

The <Formik/> component has a onSubmit prop but not an onChange 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.

@mintyPT
Copy link
Author

mintyPT commented Jun 24, 2019

@buglessir

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 <Formik> component. I was aware of the handleChange but I need something higher on the hierarchy. I want a way to be notified (and make changes to the values), every time something changes.

@mintyPT
Copy link
Author

mintyPT commented Jun 25, 2019

@buglessir

For those seeing this after the fact, here's what happened:

  1. I posted a question (seen above);
  2. @buglessir did not properly read the question and posted a totally wrong answer;
  3. I posted a reply to the answer (can be seen above);
  4. @buglessir removed his comment and added a "thumb down" on my reply and question.

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.

@kmmm16
Copy link

kmmm16 commented Jun 25, 2019

Maybe you can use validate method to achieve yours goal. Additionally set validateOnChange to true.

@mintyPT
Copy link
Author

mintyPT commented Jun 25, 2019

I thought about that. I might fall into a loop: validate -> change data -> validate

We come to love not by finding a perfect person, but by learning to see an imperfect person perfectly.
Sam Keen

@kmmm16
Copy link

kmmm16 commented Jun 25, 2019

You are talking about empty inputs (I belive all of them are input values for yours Form), so checking if those are empty is a kind of validation IMO. So you got: empty validation check -> custom validation without loop.

@mintyPT
Copy link
Author

mintyPT commented Jun 25, 2019

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.

The best way to pay for a lovely moment is to enjoy it.
Richard Bach

@kmmm16
Copy link

kmmm16 commented Jun 25, 2019

So maybe just go to use onChange on <Form> component?
Example:

<Form onChange={() => console.log("test")}>
    <div className="container">
        <Field type="text" name="names" />
    </div>
</Form>

@mintyPT
Copy link
Author

mintyPT commented Jun 26, 2019

Is this something you used?

I looked at the code. All that <Form> does is setting up onSubmit and onReset. The rest of the props, are simply forwarded to <form> and as far as I know, <form> does not take an onChange prop.

So, either I'm dumb, or your answer makes no sense.

Does someone know if it's possible to do this?

@kmmm16
Copy link

kmmm16 commented Jun 27, 2019

Yes I used it and it works... Just try...

@mintyPT
Copy link
Author

mintyPT commented Jul 2, 2019

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

@Unsfer
Copy link

Unsfer commented Aug 5, 2019

onChange on <form> is just a native form's event, it has nothing to do with formik. I think @mintyPT want a way to setup a callback that will be triggered on each change of formik's state (values) via handleChange, setFieldValue, field.onChange ...

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);

@gaspardip
Copy link

You can do this today with a formik effect I think. Not quite an ideal solution.

import { connect } from 'formik';
import noop from 'lodash/noop';
import { useEffect } from 'react';
import usePrevious from 'react-use/lib/usePrevious';

const FormikEffect = ({ onChange = noop, formik }) => {
  const { values } = formik;
  const prevValues = usePrevious(values);

  useEffect(() => {
    // Don't run effect on form init
    if (prevValues) {
      onChange({ prevValues, nextValues: values, formik });
    }
  }, [values]);

  return null;
};

export default connect(FormikEffect);

Usage

<Formik validate={validateCallback} {...props}>
      {props => (
        <>
          <FormikEffect onChange={onChange} />

@keeprock
Copy link

Hey, just run some method outside of Formik like this:

render() {
    const getData = params => {
        console.log(">>> params", params)
    };

    return (
        <Formik
            initialValues={{firstName: 'test'}}
        >
            {
                ({handleChange, handleSubmit, values, dirty}) => {
                    getData({dirty});
                    return (
                        <View>
                            <TextInput
                                onChangeText={handleChange('email')}
                                value={values.email}
                                label="Email"
                                placeholder="Write down your email"
                            />

                            <Button onPress={handleSubmit} style={styles.button}
                                    title={"Submit"}>Submit</Button>
                        </View>
                    )
                }
            }
        </Formik>
    )
}

getData will run every time something happens with Formik, e.g. onChanged.

@Unsfer
Copy link

Unsfer commented Aug 29, 2019

As I see getData will run on every render, e.g. onChange, onBlur, props/state change, ... cos there is no shouldComponentUpdate in <Formik> component.
So it's a wrong behavior unless you want to filter getData calls

@johnrom
Copy link
Collaborator

johnrom commented Aug 29, 2019

@Unsfer the way I'd recommend to handle this is writing a FormikEffect-like component, as described by @gaspardip . It does exactly the same thing as Formik having a shouldComponentUpdate. It doesn't seem ideal until you realize your project may need onChange to be triggered during different circumstances than another project. For example, if the fields change, but have the same value, you may want the onChange to trigger anyway, while someone else may not. Adding configuration for all this is not maintainable, so I'd recommend spinning up your own. @jaredpalmer I think this info could go in documentation since there are many issues related to it, or we could recommend a userland component that does this, similar to https://github.com/jaredpalmer/formik-persist. It's a very common request, so I wonder if we should identify a proper way to do it that is flexible, and provide a solution. There are many related issues like:

#271
#1750
#172
#1106

@johnrom
Copy link
Collaborator

johnrom commented Aug 31, 2019

@mintyPT if it works for you, I'd like to close this issue in favor of documenting this implementation in #1787

@AlonMiz
Copy link

AlonMiz commented Oct 22, 2019

It would be nice to have an onChange method to Formik with the latest form values.

@johnrom
Copy link
Collaborator

johnrom commented Oct 22, 2019

@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.

@AlonMiz
Copy link

AlonMiz commented Oct 23, 2019

@johnrom From my perspective, it should be similar to the way validate is being triggered.
which is at least: on value change, blur, focus etc.
then you could use it as you will (ignore cases where values are similar)

@johnrom
Copy link
Collaborator

johnrom commented Oct 24, 2019

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 formik.onChange doesn't work the way everyone wants it to at the same time, I think it makes sense to allow a completely configurable implementation. Using Effects as documented above and implemented in the repo I linked above is a completely configurable way to implement it, and you can target any internal change of state that formik exposes via context. You can also add multiple different ones targeting different state changes.

The only other place I could recommend implementing something like this would be onDispatch when Formik actually dispatches actions inside of its state. However, I'm not sure that every formik state change occurs within a dispatch (blur and focus don't, but touched does). It might confuse users since they would then have to instead check for messages:

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.

@williamluke4
Copy link

williamluke4 commented Nov 30, 2019

@johnrom I think implementing the onDispatch prop would we a great idea. Though would something like this be possible, as you are dispatching another action during the current action?

<Formik
     onDispatch={(message) => {
        if(message.type === 'SET_VALUES' && payload.field === 'rate'){
            formik.setFieldValue('total', payload.value * formik.values.qty)
        }
        return message
    }
}>
    ...
</Formik>

@williamluke4
Copy link

williamluke4 commented Nov 30, 2019

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)
}}/>

@stale stale bot added the stale label Jan 30, 2020
@stale stale bot removed the stale label Feb 17, 2020
@fonbrauzen
Copy link

In case someone still interested (ts + Formik + MS Fabric UI for controls (but could be any other custom controls))
for sure decoration with change handler can be done also with UseEffect or HOC.
form example:
<form onReset={formikBag.handleReset} onSubmit={formikBag.handleSubmit} {...formikBag} > //currently Formik have problems in ts with Form, so I used fallback <FabricDropdown label={/*my label*/ ?? undefined} name={//field name, the same one provided to formik} onchangecallback={this.handleFieldChange} //bind to the form class method according to react form docs placeholder={/*my placeholder*/ ?? undefined} /> <button type="submit">Submit</button> </form>

and control:
`const FabricDropdown: React.FunctionComponent<{ label; name; onchangecallback; placeholder }> = ({ label, name, onchangecallback, placeholder }): JSX.Element => {
FabricDropdown.propTypes = {
label: PropTypes.string,
name: PropTypes.string.isRequired,
onchangecallback: PropTypes.func.isRequired,
placeholder: PropTypes.string
};
const [field, meta ] = useField(name);
// other non related code
const changeHandler = (event: React.FormEvent): void => {
field.onChange(event); //standard Formik event receiver
onchangecallback(event); // my custom handler
};

return (
<>
<Dropdown
label={label}
onChange={changeHandler}
placeholder={placeholder}
options={//my function for getting possible values}
errorMessage={meta.touched && meta.touched ? meta.error : undefined}
/>
</>
);
};`

@asologor
Copy link

asologor commented Mar 10, 2020

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>

delay prop is optional. Default delay is 0 (i.e. immediate triggering). Delay may prevent UI lags in some cases, but it depends on your onChange logic.

If you want to use this code as-is, you'll need to install the dependency hook first:

npm install --save use-deep-compare-effect

@felixroos
Copy link

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.

@fonbrauzen
Copy link

fonbrauzen commented Mar 22, 2020

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.
Only in one control I had to in a bit hacky way tell Formik about new field value.
A bit ovewritten change event, because I don't have name property for this control:
event.target['name'] = fieldName; event.target['value'] = fieldValue ?? undefined; field.onChange(event);

@asologor
Copy link

asologor commented Mar 23, 2020

@felixroos should work well with simple forms, but I see two drawbacks so far:

  1. Change event occurs on the first render
  2. Won't work well with array/object values (FieldArray and maybe some custom fields)

PS: haven't tested though.

@littlehome-eugene
Copy link

@felixroos how did you overcome React Hook "useEffect" cannot be called inside a callback. React Hooks must be called in a React function component or a custom React Hook function ?

@johnrom
Copy link
Collaborator

johnrom commented May 19, 2020

@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.

@vojtechportes
Copy link

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>
  )
}

@vigneshwaran-chandrasekaran

@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.

Thank you

@nickmccurdy
Copy link
Contributor

onChange doesn't seem to even fire a native DOM event any more. I'm working around this by just using values from the render prop directly, but it's not quite as ergonomic (especially if you're using class components which can't use useEffect and require you to pass values to componentDidUpdate). I've also opened #2675 which I hope would make this use case easier (by letting you omit onSubmit without losing the desired change functionality).

@nickmccurdy
Copy link
Contributor

nickmccurdy commented Aug 10, 2020

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

@twhitmorenz
Copy link

twhitmorenz commented Sep 24, 2020

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:

  1. simple to code,
  2. trivially easy for simple cases,
  3. easily & fully customizable for other cases.

Simple example:

handleChange (e, formik) {
    updateHeadlineName( formik.texts.name);
}
<Formik onChange={handleChange}>

Or more concisely:

<Formik onChange={(e, formik) => updateHeadlineName( formik.texts.name)}>

More complex example, with eg. conditions on the field & event:

handleChange (e, formik) {
    if (e.type === 'onblur' && e.target.name === 'name') {
        updateHeadlineName( formik.texts.name);
    }
    window.alert('Name changed to: '+formik.texts.name);
}
<Formik onChange={handleChange}>

Dependent field example -- note how logic is separate from the underlying fields, and composes with them. This is an architectural advantage of event handlers:

handleChange (e, formik) {
    if (e.type === 'onchange' && e.target.name === 'state') {
        formik.setFieldValue('city', '');
    }
}
<Formik onChange={handleChange}>

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

@johnrom
Copy link
Collaborator

johnrom commented Sep 24, 2020

@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 executeChange handler to do something completely different. A prop like afterChange might be acceptable, but it's important to note that everything outside of the event in this case would be stale in the current API.

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 value, touched, and errors. Or, we can do both, which I think makes the most sense.

@nghiaht
Copy link

nghiaht commented Sep 27, 2020

Hello all,
I use the sample from https://formik.org/docs/api/useFormikContext, change the AutoSubmitToken into something like AutoSubmitElement. Make it call the submitForm when values changes.

@twhitmorenz
Copy link

twhitmorenz commented Sep 28, 2020

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'm not sure it's quite correct to think that developers would want to replace/ lose this necessary basic functionality in the vast majority of cases. Since the field/ forms in question become largely non-functional.
  • If we are still talking event handlers, delegating on to the formik "instance" or returning true/false might be reasonable ways to invoke or prevent execution of the default functionality (depending on synchronicity) in the rare case they needed to skip it.

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.

  • If event handlers aren't workable/ recommended, I think it would be really important to clearly document the recommended pattern.
    • Including a pattern that works for class components; not solely for instance components.
    • Placing logic to be outside the components, eg. not the monolithic anti-pattern currently given in https://formik.org/docs/examples/dependent-fields.
    • And preferably including information (eg. an event or delta) to show what has changed.
  • I'm good with the idea of a reducer hook/ delegate; again, it's important to be able to delegate normal behavior to Formik's existing functionality & to have an easy way to access that.
  • I'm also good with an API (instance or pseudo-instance) to get the latest Refs of value, touched and errors.
    • Can I clarify -- would this be usable from event handlers? If not, where would it be useful from?

From a high-level perspective of maximizing simplicity:

  • I assume I (and many thousands of others) would still be very keen to use event handlers, if possible.
  • lack of a Formik instance ( or pseudo-instance) able to access state is a general barrier.
  • from a perspective of simplicity, I would think values and touched could quite reasonably be synchronous.
  • I'd also like to be able to use class components. I'm not at all convinced that being forced to use a vague & complicated set of workarounds to achieve statefulness in a stateless function, is necessarily simpler or better.

Lastly John, some specific questions:

  • when you say useEffect(), could I use componentDidUpdate() instead?
  • or are you suggesting an approach that only works for function components, which are not what we're using?

Thanks,
Regards, Tom

@johnrom
Copy link
Collaborator

johnrom commented Sep 29, 2020

@twhitmorenz this is a lot to respond to, but I'll break it into sections

Replacing Formik Defaults, the onChange handler definition

I'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 Formik.executeChange. Maybe I'm wrong, and that's OK. The natural flip side of this is that once Formik.executeChange is completed and all of Formik's state has been updated, then you would use an effect to capture that state change (the state change is not an event triggered by a user). This is a Side Effect triggered by other Side Effects triggered by an Event triggered by a user.

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 preventDefault(). However, I don't think the DOM API is very good in this sense anyway. React says DOM API bad, React event listener API good. The React event listener API is functional, and only has one listener which to do multiple things must be chained:

<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 useEffect.

Class v Function components

I'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.

componentDidUpdate should work if you are able to get props from React hooks. Formik uses React hooks, and you can access its values from a class component via a render callback, but you cannot call componentDidUpdate based on a change in the render callback. Well, you can hack it if you want I guess.

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>
  }
}

@omarish
Copy link

omarish commented Nov 5, 2020

You can also do this through the Field API if you want an onChange behavior for a specific field:

<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.

@777akot
Copy link

777akot commented Jun 23, 2021

uhm... i just add this to validation of fields which values state was needed
.test( 'c', 'c', ( value ) => { outsideFunction( value ); return true } )

How bad is this? :)

@ddluc
Copy link

ddluc commented Sep 30, 2021

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:

import React from 'react'; 
import { useFormikContext } from 'formik';

const FormListener = ({ onFormChange, fields }) => {

  const { values, isValid } = useFormikContext();

  React.useEffect(() => {
    onFormChange(values, isValid);
  }, [...fields.map((field) => values[field]), isValid]);
  
  return null;
  
}; 

export default FormListener; 

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 ofFormListener will report back to the root form component when any specified field is updated:

<FormListener onFormChange={onFormChange} fields={['email', 'name', 'username']} />

The onFormChange callback is only invoked when one of the specified field values changes but does nothing when any other field is changed. In my use case, I also wanted the listener to fire once the form was valid, but you could remove that from the effect dependencies if you didn't need that behavior.

FWIW, I don't think that adding an onChange handler directly to the Formik component is a good idea. It's more likely to be misused than it solves any issues, and creating this behavior on your own is simple enough using the tools the library already provides.

@gpoole
Copy link

gpoole commented Oct 22, 2021

@vojtechportes that's a nice general solution, just watch out that in your code you're also going to be invoking onChange if the onChange function itself changes and also on mount as mentioned above. Since the handler is named onChange the behaviour might be surprising from the outside to someone expecting it only to be called when something actually changes, and if there are any side effects this might be a problem.

@johnrom
Copy link
Collaborator

johnrom commented Oct 22, 2021

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.

@m4xleb
Copy link

m4xleb commented Nov 3, 2021

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 <form onChange={ !standalone ? (e) => handleChange(e,formikProps.values) : false}>inside the Fomik component

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.

@yasir-systemplus
Copy link

  useEffect(() => {
      console.log('changed', values);
    }, [values]);

Hooks cannot be called inside callbacks. Ref

@johnrom
Copy link
Collaborator

johnrom commented Nov 29, 2021

@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 getState and make multiple changes at the same time without losing state.

@priusGit
Copy link

priusGit commented Nov 16, 2022

@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 dirty, isValid, isSubmitting changes to disable the save button somewhere else in the app.

@johnrom
Copy link
Collaborator

johnrom commented Nov 16, 2022

@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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests