Formik: Type-safe implementation of setFieldValue

Created on 19 Mar 2019  路  6Comments  路  Source: formium/formik

馃殌 Feature request

Current Behavior

At the moment, to set a field value, you have to call a method with the following signature:

setFieldValue(field: string, value: any)

When using Formik with TypeScript, this method isn't "safe": you could specify a field that doesn't exist, or specify a value that doesn't confirm to the type signature.

Desired Behavior

A setFieldValue (or similar) method that takes a "partial" object would allow for writing a TypeScript definition similar to setState, for example:

setFieldValue({ name: "Sam", zipcode: "12345" })

And a definition like (pseudocode):

setFieldValue<K extends keyof Values>(value: (Pick<Values, K> | Values | null)): void;

Suggested Solution

I know that TypeScript isn't given first-class support by the maintainers, but I'm interested in knowing your thoughts on such a setFieldValue method: I would be happy to raise a PR with this change and write the associated TypeScript definitions if you think this is a good idea.

Who does this impact? Who is this for?

TypeScript users.

Describe alternatives you've considered

I couldn't think of any, but I'd be interested to know if there's an alternative.

stale

Most helpful comment

We ran into this in a project we're working on as well. I think this signature might work for setFieldValue (making the "value" typesafe as well):

export interface FormikActions<Values> {
  setFieldValue<Field extends keyof Values>(
    field: Field,
    value: Values[Field],
    shouldValidate?: boolean
  ): void;
}

All 6 comments

That new signature could work nicely, but I suggest it just be defined as another callback, aptly named setFieldValues (plural).

The current method of setFieldValue needs its signature fixed. The field parameter should be restricted so its only a key of the Values generic. I'm not sure why its just plainly defined as a string, but its concerning to me.

setFieldValue(field: keyof Values, value: any): void;

@justinbhopper That makes sense: and you're right, setFieldValues is a much better name. I might give this a shot, if people feel like it's a sensible idea.

I certainly would use it. I think this would be the signature you would use:

export interface FormikActions<Values> {
  setFieldValues(values: Partial<Values>): void;
}

You'd roll through each property in values one by one, probably using a for in. You should accept the value, even if its undefined -- don't make the mistake of skipping/ignoring undefined values, because currently formik allows setting a field's value to undefined in setFieldValue.

We ran into this in a project we're working on as well. I think this signature might work for setFieldValue (making the "value" typesafe as well):

export interface FormikActions<Values> {
  setFieldValue<Field extends keyof Values>(
    field: Field,
    value: Values[Field],
    shouldValidate?: boolean
  ): void;
}

this is stale?

Definitely not stale, but keyof implementations are _extremely broken_. This needs to support nested fields. See work at https://github.com/johnrom/formik-typed for example of a workaround. Combined with extending #1336 to all of Formik itself (it currently just strengthens fields, and is way out of date) would make it work, but unfortunately it uses a Proxy and Proxies are not supported in IE, so not many real world projects could make use of it.

Other ideas on implementations:

interface Person {
   name: {
      first: string,
      last: string,
   }
   friends: Person[];
}
const index = 0;

//
// this is what I'd like to see, but I have no idea how it could be implemented without proxies.
//
setFieldValues<Person>(values => values.friends[index].name.first)("john");

//
// these would be limited by the number of type aliases we wanted to support
// and be extremely hard to maintain in typescript
//
setFieldValue<Person>("friends", index, "name", "first")("john"));
// linq-esque
setFieldValue<Person>(
  () => "friends",
  () => index,
  () => "name",
  () => "first"
)("john");

//
// these are ugly as heck
//
setFieldValue<Person>(() => "friends")(() => index)(() => "name")(() => "first")("john");
// why am I still trying
setFieldValue<Person>(() => [
  "friends, 
  () => [
    index, 
    () => [
       "name",
       () => 
        "first"
    ]
  ]
])("john");

//
// these are proxies so not supported in IE
//
// this one is basically https://github.com/johnrom/formik-typed
() => 
  <Formik<Person>>
    {fields => fields.friends[0].name.first._setValue("john");}
  </Formik>;
// also kinda ugly and weird
setFieldValue(getField<Person>().friends[0].name.first._getName(), "john");

Other than that, the best type-safe-ish implementation is:

// name is not type safe
const [field, meta, helpers] = useField<string>("friends[0].name.first");
// but setValue is!
helpers.setValue(0); // ERROR: should be a string

Welcome to other ideas, but none of these APIs are something I'd personally be interested in implementing until proxies are usable on the web.

Was this page helpful?
0 / 5 - 0 ratings