formik
formik copied to clipboard
Type-safe implementation of setFieldValue
🚀 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.
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.
Is there any 2022 workaround for this? Seems still being an issue.
I'd love an update on this as well! But definitely needs to support nested keys which I think is the crux of the problem.
This would be a lovely addition: https://dev.to/pffigueiredo/typescript-utility-keyof-nested-object-2pa3!
Happy to open a ticket if the team would be open to it.
Is there any 2022 workaround for this? Seems still being an issue.
Hey all, I managed to implement full typesafe solution, which also supports nested fields.
See demo here.
type SampleFormikSchema = {
age: string;
address: {
city: string;
houseNumber: number;
additionalProperties: Record<string, number>;
};
hobbies: string[];
gender?: "male" | "female"
salary: number | null
};
setFieldValue("address.additionalProperties.someNumericValue", 123); // 👈 full autosafety for field name and value here ✅
Feel free to copy to your codebase. Or if I get enough votes, I could create PR. What do you think @jaredpalmer?
Note: for accessing arrays, it is working only with dot notation, not brackets.
I created a function that does the trick:
type UpdateField = <T extends keyof FormikSchema>(field: T, value: FormikSchema[T]) => void;
const updateField: UpdateField = (field, value) => {
formik.setFieldValue(field, value);
};
Then you can use updateField(...) and its params will have the types based on your FormikSchema for type-checking a auto-complete purposes.
Extra tip:
If you combine your from with yup you can infer its type as follows:
export type FormikSchema = Partial<Yup.InferType<typeof validationSchema>>;