jaredpalmer / formik

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

[v2]: Add parse and format props to useField/Field #1525

Closed jaredpalmer closed 4 years ago

Andreyco commented 5 years ago

Yes please. I am curious about useField API. Do you have anything on your mind already?

Ciaran0 commented 5 years ago

This would be game changing

klis87 commented 5 years ago

For now I did such stuff in my custom fields, adjusting onChange and value, would be cool to have it built-in!

Fauntleroy commented 5 years ago

This is exactly what I need right now. I've got lots of glue code in unreliable places converting ids to numbers all over the place as it stands.

johnrom commented 5 years ago

@jaredpalmer this is what I meant by suggesting a valueConverter callback, but these names make so much more sense πŸ‘

I'd like to suggest basing this off of #1334 so parse and format have access to real types in TypeScript. Also to recap something I was thinking about as part of this, it'd be nice to not require the parse / format props when a field has a string value (since html inputs are always strings unless you hackishly modify their value), but to require it when a field has a different value type. I couldn't figure out how to make TS+React play nicely there.

Also, I'm not sure if React Native inputs always use strings because I'm overall not familiar with that.

joepuzzo commented 5 years ago

https://joepuzzo.github.io/informed/?path=/story/formatting--format-and-parse If you want to take a look at how I've implemented format + parse, and mask ( Might be helpful )

szimek commented 5 years ago

I'm currently implementing a credit card payment form and was looking at how Stripe forms behave and they are doing pretty complex stuff with setting cursor position etc. especially with the expiration date input. E.g. when you type 10, it will display 10 / and move the cursor at the end, so that you can enter the year. If you then press backspace, it will display 10, removing / at the end. In both cases, the parsed value for month would be 10 (unless they don't parse incomplete fields at all), but the formatted (i.e. displayed) value is different, once it's 10 / and once it's 10. So it looks like for such use case, format function would not only need access to the current values, but to the last event as well. Another issue is that one input provides 2 values - expiration month and year.

However, maybe parse and format props don't have to support such complex use cases.

joepuzzo commented 5 years ago

Something like this https://joepuzzo.github.io/informed/?path=/story/formatting--mask-with-cursor-offset ?

szimek commented 5 years ago

@joepuzzo Yeah, though I'm not sure if you can replicate Stripe behavior exactly, if the mask function doesn't additionally have access to the event that triggered the change, because as I described above, for the same input value (e.g. 10), it will display either 10 / or 10.

You can play with Stripe forms here: https://stripe.dev/elements-examples/

johnrom commented 5 years ago

Interesting! The parse callback would definitely have access to the last value since it could check event.target.value which would be the old raw value before setFormikValue() is called. In this event, if the internal representation of the field in Formik is a string, the parse function can return 10/ or 10 depending on the circumstance. However, if the internal representation in formik is month: number, year: number, then something special would have to happen. For example, if the internal representation of the field is an object, it could like look like: { month: number, year: number, isLeading: boolean }.

Overall I think the stripe example could work with a simple API like this, but exposing the last value to the format function can also be useful, for example if we need to compare current value and last value to determine the index of the input cursor. It could be "noted" by the parse function and used later by returning an object like { value, cursorIndex }. A lot of masking breaks down when the user starts to delete from the middle like changing a middle number in their phone number, or when selecting multiple characters + deleting or typing. I'm interested to see if anyone has more experience with that functionality specifically, or if it's outside the scope of this API.

Edit: nevermind apparently that part is covered above :P

szimek commented 5 years ago

@johnrom I'm using https://github.com/medipass/react-payment-inputs with Formik to handle a credit card form and while looking into fixing some of its issues (https://github.com/medipass/react-payment-inputs/issues/2) I arrived at similar conclusions ;) This library does not handle parsing (or at least it does not expose the parsed values to Formik, though it needs to parse them internally to validate them). I had to additionally pass the previous value to its formatExpiry function, which currently only accepts an event. I had to return not only the formatted value, but also, optionally, the new caret position, to handle cases like e.g. when you got value 02 / and try to delete 0 - in such case Stripe does not delete 0, only moves the caret to the beginning.

joepuzzo commented 5 years ago

@szimek maskWithCursorOffset does use the event to track the location so it is possible. If you look at that example it does exactly that

jgoux commented 5 years ago

Hello all, as we're discussing adding a way to manipulate a field's internal value vs the displayed value, what are your thoughts about manipulating a field's value before it gets passed to onSubmit ? My typical use case is for parsing numbers for example, I always have to do it in a centralized manner in onSubmit, but it could be done at the field level so we can drop components like NumberField directly into a Form and get the expected value in onSubmit.

I asked about it on final-form's repo : https://github.com/final-form/final-form/issues/242#issuecomment-524868187 but I'd love to have other opinions.

johnrom commented 5 years ago

@jgoux this would be done via onParse in the API above, meaning Formik's internal value would match that which you expect to submit. So if you did onParse={convertStringToNumberCallback} and onFormat={convertNumberToStringCallback}, your submission would always have the numeric value and not the string value.

jgoux commented 5 years ago

@johnrom The issue I have with using onParse and onFormat for this purpose is that you have to reimplement all the browser native logic to parse and format a number. For example, what do you expect to see when the user is typing a float like 2.? You'd have to track the separator in order to format it correctly. Or if you type 13.0, how do you keep track of the last 0 in order to go to 13.08?

This doesn't seem much but it's for this kind of things that we have input types other than text πŸ˜„

What I want is to rely as much as possible on the browser, and just apply a transformation when the user is done filling the form, but at the field level. Does that make sense?

johnrom commented 5 years ago

@jgoux You bring up an interesting point. There are two different types of parsing we are talking about here. One reacts to a user's typing, formats it for their own UX, and prints it back out at them (like phone number masking), and another takes the internal representation and formats it for submission to the server (maybe the server actually just wants 5555555555). I'm not sure whether we should solve them both at once or keep separate issues for them.

joepuzzo commented 5 years ago

I solved this issue in informed. It’s totally doable. https://joepuzzo.github.io/informed/?path=/story/formatting--format-and-parse

mehrdad-shokri commented 5 years ago

Yes, I have 2 use cases that make me think this is a valid requirement from a form validator.
First use case:
I have a tuition Field which utilizes a number formatter. But the problem is that API requires the tuition as an integer without ,s.
My approach to this problem was using state as the value of tuition with , and manually setFieldValue by my self.
Second use case:
I'm using material-ui-picker DatePicker as a formik Field everything goes right except API requires me to give the date in YYYY-MM-DD HH:mm:ss format instead of a timestep.
My solution was like use case 1 but again I thought there should be an easier way for this common problem

jonlambert commented 4 years ago

Apologies for the bump, but this functionality would be extremely useful to have. Is it still on the cards?

johnrom commented 4 years ago

I'm very interested in this functionality, just haven't had any time to implement.

MikeSuiter commented 4 years ago

I solved this issue in informed. It’s totally doable. https://joepuzzo.github.io/informed/?path=/story/formatting--format-and-parse

@joepuzzo So does this only work with the Informed library or have you also figured out how to make it work with Formik?

I have format/normalize hacked into Formik 1 using a custom Field that renders a Formik Field but trying to get it into Formik 2 now and struggling. Formik Material UI library complicates this because they don't pass down onChange, value, etc.:

https://stackworx.github.io/formik-material-ui/docs/guide/faq

joepuzzo commented 4 years ago

@MikeSuiter To my knowledge its only an informed thing. I maintain Informed and not Formik so I am unsure. All this being said, informed does everything that Formik does so using it as an alternative should be fine :)

johnrom commented 4 years ago

I create wrappers for Field myself, pass them Field, and do onParse and onFormat myself, and it works fine. It would definitely be a bonus if it was implemented in Formik itself, but on the TypeScript side it would depend pretty heavily on strongly typed fields (#1334). I built my project off of this PR (#1336) which implements typed fields, but it's not based off of v2.

redtogin commented 4 years ago

I assume there are some non-trivial complexities and api choices involved in adding the value cycle for format and parse. And I assume those choices might lock things in, so I would like to just add that I think it would be very nice if format and parse could be implemented in a way that _input masking could be added too later_ .

The common use-case I have is a date field with DD-MM-YYYY input, where I would like the dashes to be automatically entered as the user types, and the initial and submit value to use YYYY-MM-DD. So eventually to be able to create a standalone <DateField name='..' ../> component without form-level machination, using formik's parse, format, mask(?) field props.

If I remember correctly redux-form can't do this either. normalize can be misused for masking but then it's not possible/easy to use parse and format alongside for this use-case.

terenceodonoghue commented 4 years ago

Came here looking for this very solution. πŸŽ‰ Looking forward to seeing the PR merged.

rochfeu commented 4 years ago

Here is my solution to add a parse function.

import React from 'react'; 
import { Field, useField } from 'formik';

export default function MyFormatField ({name, validate, parser, ...others}) { 
  const [,, { setValue }] = useField({name, validate});
  return (
    <Field
      name={name}
      onChange={event => {setValue(parser(event.target.value))}}
      {...others}
    />)
};
jaredpalmer commented 4 years ago

This is now available in 3.0.0-next

maddhruv commented 4 years ago

Closing as this is going to get released in upcoming major i.e. 3.0.0

kelly-tock commented 3 years ago

@johnrom do you have any examples of what you did in the past you could share?

https://github.com/formium/formik/issues/1525#issuecomment-578950642