Skip to content

Instantly share code, notes, and snippets.

@kangabru
Created August 20, 2020 20:14
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save kangabru/5d6f1b2a10bcbd82fc20bad45dad59a0 to your computer and use it in GitHub Desktop.
Save kangabru/5d6f1b2a10bcbd82fc20bad45dad59a0 to your computer and use it in GitHub Desktop.
Adds expected value types to FormData
// Before: untyped 😥
const form1 = new FormData()
form1.get('url') // Is 'url' expected?
form1.append('image', '?') // Do I pass in a string or blob?
// After: same functionality but with types 😁
const form2 = TypedFormData<{ title: string, image: Blob }>()
form2.get('url') // Throws error
form2.set('title', "Hello Github!")
form2.append('image', {} as Blob) // Error if string is used
/** Return a standard FormFata object but wrapped with expected types */
function TypedFormData<T extends FormDataKeys<T>>(): TypedFormData<T> {
return new FormData() as any as TypedFormData<T>
}
/** Only allow properties with 'string' or 'blob' types.
*
* @howitworks
* What is it doing?
* Defines an object in the form:
* { [key]: key type }
*
* [K in keyof T]
* - Pulls out keys 'K' from type 'T'
* - example: T = { key1: string, key2: number }
* - result: K = 'key1' | 'key2'
*
* T[K]
* - Returns the type for the given key
* - example: T = { key1: string, key2: number }
* - result: T['key2'] -> number
*
* ValType extends string | Blob ? ValType : never;
* - Throw error if ValType isn't a string or blob
* - 'never' means the type isn't possible which throws the error
* */
export type FormDataKeys<T> = {
[K in keyof T]: T[K] extends string | Blob ? T[K] : never;
}
/** Overwrite FormData 'get', 'set, and 'append' function with expected types.
*
* @howitworks
* What is it doing?
* Overrides FormData type with custom 'get', 'set', and 'append' functions
*
* Omit<FormData, 'get' | 'set' | 'append'>
* - Returns FormData type without 'get', 'set', and 'append' properties
*
* get: (key: keyof T) => T[typeof key]
* - Defines a 'get' function
* - Only permits calls with keys from T
* - Returns the correct value for the given key
* - Example:
* T = { key1: string, key2: number }
* get('key1') -> string
* get('key2') -> number
* get('key3') // error
*
* set: <K extends keyof T>(key: K, value: T[K]) => void
* - Defines a 'set' function
* - Only permits calls with keys and values from T
* - Example:
* T = { key1: string, key2: number }
* set('key1', '1')
* set('key2', 2)
* set('key1', 1) // error: not string
* set('key2', '2') // error: not number
* set('key3', '3') // error: invalid key
* */
type TypedFormData<T extends FormDataKeys<T>> = Omit<FormData, 'get' | 'set' | 'append'> & {
get: (key: keyof T) => T[typeof key] | undefined,
set: <K extends keyof T>(key: K, value: T[K]) => void,
append: <K extends keyof T>(key: K, value: T[K]) => void,
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment