-
Notifications
You must be signed in to change notification settings - Fork 12.6k
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
FormData methods should take a Generic #43797
Comments
How about making FormData itself generic? So we can do something like this: const formData: FormData<Person> = new FormData(formEl); |
I am looking forward to it, it is very useful to me. |
Here is my solution: define a custom FormData interface IRuntimeForm {
[key: string]: any;
}
export class RuntimeForm<T extends IRuntimeForm> {
constructor(private _form: T) {}
public formData(): FormData {
const form = new FormData();
for (const key in this._form) {
if (this._form[key] !== undefined) {
form.append(key, this._form[key]);
}
}
return form;
}
}
new RuntimeForm<{ foo: string }>({ foo: "bar" }) |
yes that'd be great |
thanks, nice temp solution. |
export class RuntimeForm<T extends IRuntimeForm> {
constructor(private form: T) {}
public formData(): FormData {
const form = new FormData();
Object.keys(this.form).forEach((key) => {
if (this.form[key] !== undefined) {
form.append(key, this.form[key])
}
})
return form;
}
} If anyone is using AirBnB rules and wants the above to work |
Just wanted to note that this'd be a very welcome feature, especially as some newer frameworks like Remix put FormData front and center. |
I started using |
|
+1 on this suggestion. Remix's reliance on FormData is making this much more important |
+1 |
4 similar comments
+1 |
+1 |
+1 |
+1 |
+1. I was trying to correct the type of a FormData.fromEntries and this was the first thing I tried, just to arrive here after looking around how to solve this. |
+1, this would be very useful on Remix and new react router based projects |
+1 |
+1, same using remix. |
Yes please +1 |
+1 |
1 similar comment
+1 |
Please stop with the +1's. Everyone subscribed to this issue gets a notification for it. Use the emoji reactions on the first post instead. |
Any news on this ? |
This is what I ended up doing: type TypedFormDataValue = FormDataEntryValue | Blob
/**
* Polyfill for FormData Generic
*
* {@link https://github.com/microsoft/TypeScript/issues/43797}
* {@link https://xhr.spec.whatwg.org/#interface-formdata}
*/
interface TypedFormData<T extends Record<string, TypedFormDataValue>> {
/**
* Appends a new value onto an existing key inside a FormData object, or adds the key if
* it does not already exist.
*
* {@link https://developer.mozilla.org/en-US/docs/Web/API/FormData#formdata.append}
*/
append<K extends keyof T>(name: K, value: T[K], fileName?: string): void
/**
* Deletes a key/value pair from a FormData object.
*
* {@link https://developer.mozilla.org/en-US/docs/Web/API/FormData#formdata.delete}
*/
delete(name: keyof T): void
/**
* Returns an iterator allowing to go through all key/value pairs contained in this object.
*
* {@link https://developer.mozilla.org/en-US/docs/Web/API/FormData#formdata.entries}
*/
entries<K extends keyof T>(): IterableIterator<[K, T[K]]>
/**
* Returns the first value associated with a given key from within a FormData object.
*
* {@link https://developer.mozilla.org/en-US/docs/Web/API/FormData#formdata.get}
*/
get<K extends keyof T>(name: K): T[K] | null
/**
* Returns an array of all the values associated with a given key from within a FormData.
*
* {@link https://developer.mozilla.org/en-US/docs/Web/API/FormData#formdata.getall}
*/
getAll<K extends keyof T>(name: K): Array<T[K]>
/**
* Returns a boolean stating whether a FormData object contains a certain key.
*
* {@link https://developer.mozilla.org/en-US/docs/Web/API/FormData#formdata.has}
*/
has(name: keyof T): boolean
/**
* Returns an iterator allowing to go through all keys of the key/value pairs contained in
* this object.
*
* {@link https://developer.mozilla.org/en-US/docs/Web/API/FormData#formdata.keys}
*/
keys(): IterableIterator<keyof T>
/**
* Sets a new value for an existing key inside a FormData object, or adds the key/value
* if it does not already exist.
*
* {@link https://developer.mozilla.org/en-US/docs/Web/API/FormData#formdata.set}
*/
set(name: keyof T, value: TypedFormDataValue, fileName?: string): void
/**
* Returns an iterator allowing to go through all values contained in this object.
*
* {@link https://developer.mozilla.org/en-US/docs/Web/API/FormData#formdata.values}
*/
values(): IterableIterator<T[keyof T]>
forEach<K extends keyof T>(
callbackfn: (value: T[K], key: K, parent: TypedFormData<T>) => void,
thisArg?: unknown,
): void
}
function getTypedFormData<T extends Record<string, TypedFormDataValue>>(
form?: HTMLFormElement | null,
): TypedFormData<T> {
return new FormData(form || undefined) as unknown as TypedFormData<T>
} |
Pretty convenient, thanks @elving :) |
I asked for a similar feature for |
Additional use caseA typed API client function would be a use case for this: type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE';
type Options<Method> = {
method?: Method;
}
export async function fetchApi<Path extends string, Method extends HttpMethod>(
path: Path,
options: Options<Method> & {
formData: `${Method} ${Path}` extends `POST /exercise-checks`
? { id: string, file: File } // π Could be FormData<{ id: string, file: File }>
: `${Method} ${Path}` extends `PUT /exercise-checks/${number}`
? { file: File } // π Could be FormData<{ file: File }>
: never
}): Promise<
`${Method} ${Path}` extends `POST /exercise-checks`
? { id: string } | { errors: string[] }
: `${Method} ${Path}` extends `PUT /exercise-checks/${number}`
? { id: string } | { errors: string[] }
: never
> {
return '' as any;
}
const file = new File([''], '');
const goodFormData = { id: '1', file: file }; // π Could be new FormData() + .append
const badFormData = { incorrectProperty: false }; // π Could be new FormData() + .append
// β
No errors
await fetchApi('/exercise-checks', { method: 'POST', formData: goodFormData })
// β
No errors
await fetchApi('/exercise-checks/1', { method: 'PUT', formData: { file: file } })
// β
Errors, incorrect method
await fetchApi('/exercise-checks/1', { method: 'PUTzzzzz', formData: { file: file } })
// β
Error, incorrect path
await fetchApi('/xxxxx', { method: 'PUT', formData: { file: file } })
// β
Error, incorrect form data
await fetchApi('/exercise-checks/1', { method: 'POST', formData: badFormData }) |
+1 |
3 similar comments
+1 |
+1 |
+1 |
+1 |
You can check out my library for just doing this thing :) Its built on the FormData primitive so can be used in replacement of native FormData :) |
Suggestion
π Search Terms
Form Data, .entries()
β Viability Checklist
My suggestion meets these guidelines:
β Suggestion
FormData entries() method, and perhaps .keys() and .values() should take a generic.
Right now it returns FormDataEntry value and is difficult type type the returned value of that Iterable.
π Motivating Example
Example of the problem here:
https://www.typescriptlang.org/play?strictNullChecks=false&target=6#code/PTAEEEGdIVwWwKagIagGYHsBOdQBsBLAayQBcALAyUAgO1AqQBEB5AWQBpQAjGU0AO4EK+ZNwR5qCUgGMAdHIBQMjLUj9MOUAF5QwAFShypOHlD6wAAwA8muAD5FoUNboAHPgwCebhNoBE6lh0AOb+oLTIiAFoBFjq4QQAJjFxCaAAbsh4MH7+AgiQ-o7OrrQe-KQ+eUGh4ZHR-njI6ckBzelZOXncGEWO1sB29pYA3IqKIKAAKuQI9HSgcF4zAMoAXBN0pAhYaMgySAAKu5CqoADeTuhppOugtbQh484ddw+kwU-jAL4TaDBaDJSARzpBkF4ABIEAAUvniqnuJwRtAAlJdrlhpDAsPRLJCECsACQXeFnWhyWLxUg-MaKP7KVTqdDYOAAUTMuiSGBk8HmpDkAEdclgvKsJAhgdgYQByOwy1HjFRqDSspjIUioXS0BACUAAMTVGuQMLsHMVkzAAGFVBldpVyBqHjAZCQsAwMCgFjssGI8AhGSrQEljQBJH1+pC6OzqzVyfnBQowi1TG20O1YB1O4S7SMer2gDDcABWkoFlpmc1AbiwRf9uBUiGocyxXHIGD13NAoaWyBIgkKKFAyPJ3l8AH5QIHmQVqLoWCWy5Ta+zaJ8CEmQ5rw7nuP6LeCobDZxaK7MqMGMPNIDL+AJsERp3fCgAmHSgBel4HLjCr9ebsMIz3BB0RaYdTlUcYz0oahuUKLheH4C9hELIgIUncUEDgahYkgcgvDkKdlRnQoAGZ30-Jc0BXNk10TSAYS3ZAd19YDQOoQEiFoDt6DAkdIMUQ9oRhWdSNPCsWEYd1ZgwGAQmMSB7grABGQjzykAAPKI3H9GhqEgAg4B0hAuB7FQYDwJJe1oGBsjwFYZCxDUyCrIsv1IBDPAoOgiBQXpPGaLAQl2QtF2BSAgA
Feeding the .entries() Iterable into Object.fromEntries(), gives us a type of
{ [k: string]: FormDataEntryValue; }
, and has no overlap between the acutal data (Person
in my case) that is supposed to come back.So perhaps something like
formData.entries<Person>()
orformData.entries<IterableIterator<Person>>()
?I just know it's really hard to convert the result into a type. And the solutions are either
as unknown as Person
or a Type Guard, which makes me manually type each property.π» Use Cases
See above link example
The text was updated successfully, but these errors were encountered: