jaredpalmer / formik

Build forms in React, without the tears 😭
https://formik.org
Apache License 2.0
34.01k stars 2.79k forks source link

Field OnChange events & access to Formik top-level object are difficult and poorly documented #2767

Open twhitmorenz opened 4 years ago

twhitmorenz commented 4 years ago

🐛 Bug report

Current Behavior

I'm trying to add an 'onChange' event to a without losing existing behavior to update/ store the field value. By default if I specify an onChange handler, I silently lose integration with the Formik form.

What I have tried:

I am just finding this (very simple, common & basic) requirement super, super difficult to address. And really I am finding the design of Formik -- function components, no instances -- to greatly hinder the ability to access/ integrate with Formik's existing functionality.

I am preferring to use event handlers rather than coding custom components for Fields etc. I am aware there's a whole route there, where I could probably pickup the Formik Context on instantiation/ render/ whatever and probably be able to use it, but from a simplicity & framework design perspective I really strongly suggest users just be able to use event handlers.

Expected behavior

Any of the following behaviors would be fine:

  1. Allowing to take an 'onChange' property and calling that in addition to the Formik behavior.
  2. Having expose & document an 'onChange' handler. Quite possibly just forwarding the low-level event (e) or perhaps (e, formikContext/Helpers) so I can invoke Formik methods.
  3. Having
    document the 'onChange' HTML event handler.
  4. Being able to reference via a React Reference, so I can invoke the Formik behavior directly.

Reproducible example

https://codesandbox.io/s/formik-codesandbox-template-forked-mnnc6

Suggested solution(s)

I would recommend making the 'onChange' work more easily, either allowing an extra one, exposing it at the level and/ or documenting where to listen to it.

  1. Allowing to take an 'onChange' property and calling that method, in addition to the Formik behavior.
  2. Having expose & document an 'onChange' handler. Quite possibly just forwarding the low-level event (e) or perhaps (e, formikContext/Helpers) so I can invoke Formik methods.
  3. Having document the 'onChange' HTML event handler.

I would ALSO very highly recommend making it easier to access the Formik instance/ or methods. Some possible approaches here:

  1. Making an instantiated component, so I could reference it via a React Reference.
  2. Making an instantiated component, eg. with a 'this', so I could pickup Formik Context/ the Formik instance from it.
  3. Providing Formik Context to the onChange() handler somehow -- eg. as a second parameter.

Please don't assume all your users are (or should be) using 'function components'. As we see here, there seem to be difficult gaps in React with function components in that Refs are not available -- which imposes significant limits & complexity on the ability to interact with functional components. Again, simplicity doesn't argue for developers to have to create a bunch of baroque function syntax to capture the Formik Context (or whatever magic gets passed) as it goes to the children.

  1. Please also provide documentation for using Formik with normal (not function) components.

Additional context

There are many many many issues & questions raised about how to do this. Despite working at a fast pace, I literally spent hours trying numerous ineffective attempts.

The solutions, documentation & possible answers I reviewed without help included:

Possible solutions might have existed here:

Your environment

Software Version(s)
Formik 2.1.5
React 16.13.1
TypeScript no
Browser Chrome 85.0.4183.102
npm/Yarn Yarn 1.22.5
Operating System Windows 10
twhitmorenz commented 4 years ago

Also -- there seem to be some rather over-complicated ideas about how to offer 'OnChange' at the Formik level in https://github.com/formium/formik/issues/1633. If you passed the causative event & a Formik instance, handling it would be:

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

Please don't require yet another layer of overcomplexity (custom Effects, customizing Despatch, custom components) to do what a simple event handler should be able to do.

I've put some more detail on this suggestion in https://github.com/formium/formik/issues/1633#issuecomment-698095706.

Cheers, Tom

jaredpalmer commented 4 years ago

Thanks for the write up. I think you’re right. Initially, Field was a just a simple way to save some key strokes. It wasn’t supposed to do anything fancy other than get the change and blur handlers and the value.

A solution I’d like to explore to steal wrapEvent() function from Reach UI which when passed an event handler will call it after the callback (which is I think the behavior you’re describing).

onChange at the Formik level is a slightly different can of worms. In the future, we will suggest either breaking out useFormik + useEffect + FormikProvider into its own component (so we can optimize rerenders) or using one of the forthcoming selectors in #2746

johnrom commented 4 years ago

I think we need to allow a full override of onChange, because that is the purpose of the prop. Augmenting onChange is only one use case. In my opinion, conveniences to allow a user to augment onChange should come in the form of access to the original onChange handler, whch I believe are already exposed via useField or useFormikContext. We can definitely make it more intuitive to use, but I think the API should be a declarative one where the developer specifies the order of operations explicitly.

@twhitmorenz one solution to your problem is to pass Formik's handleChange to your change handler. It's not perfect because onChange will change on every render, causing Field to re-render on every change. This is why there's generally another component which subscribes to state which memoizes the change handler. This API needs significant improvement.

https://codesandbox.io/s/formik-codesandbox-template-forked-01wqz?file=/index.js

You can mix function components and class components. So you could do something like:

const MyWrappedForm = () => {
  const formikRef = React.useRef(null);

  const handleChange = React.useCallback((event) => {
    console.log(event.target.name, "Updated");

    formikRef.current?.handleChange(event);
  }, []);

  return <MyForm formikRef={formikRef} handleChange={handleChange} />;
}

class MyForm {
  render() {
    return <Formik innerRef={this.props.formikRef}>
      <Field onChange={this.props.handleChange} />
    </Formik>
  }
}

const App = () => <MyWrappedForm />;
github-actions[bot] commented 4 years ago

This issue is stale because it has been open 30 days with no activity. Remove stale label or comment or this will be closed in 60 days

github-actions[bot] commented 3 years ago

This issue is stale because it has been open 30 days with no activity. Remove stale label or comment or this will be closed in 60 days

github-actions[bot] commented 3 years ago

This issue is stale because it has been open 30 days with no activity. Remove stale label or comment or this will be closed in 60 days

github-actions[bot] commented 3 years ago

This issue is stale because it has been open 30 days with no activity. Remove stale label or comment or this will be closed in 60 days

github-actions[bot] commented 3 years ago

This issue is stale because it has been open 30 days with no activity. Remove stale label or comment or this will be closed in 60 days

github-actions[bot] commented 3 years ago

This issue is stale because it has been open 30 days with no activity. Remove stale label or comment or this will be closed in 60 days

github-actions[bot] commented 3 years ago

This issue is stale because it has been open 30 days with no activity. Remove stale label or comment or this will be closed in 60 days

krapspark commented 3 years ago

I found that Formik does expose the change handler as a render prop if you render your forms that way.

https://formik.org/docs/api/formik#handlechange-e-reactchangeeventany--void

So you can augment the default change handler with something like this:

const { extraHandler } = props
return (
  <Formik>
    {props => (
         <form onSubmit={props.handleSubmit}>
           <input
             type="text"
             onChange={(event) => {
               extraHandler(event)
               props.handleChange(event)
             }}
             onBlur={props.handleBlur}
             value={props.values.name}
             name="name"
           />
           {props.errors.name && <div id="feedback">{props.errors.name}</div>}
           <button type="submit">Submit</button>
         </form>
       )}
    }
  </Formik>
)