Construct query strings from FormData
I was wrangling some Remix TypeScript the other day and wanted to create a server action that receives submitted form data. This form data then should be used to build up a query string, make a request to a secured and hidden GET endpoint and return the result.
export async function action({
request,
}) {
const formData = await request.formData();
const queryString = new URLSearchParams(formData).toString();
const resp = await fetch(`https://hidden-api.com?${queryString}`)
}
Easy peasy, that’s what URLSearchParams
is for, right? When you check MDN, you’ll discover that passing FormData
into the URLSearchParams
constructor is valid. Nice!
[The URLSearchParams(options)
options object can be] a literal sequence of name-value string pairs, or any object — such as a FormData object — with an iterator that produces a sequence of string pairs.
Unfortunately, TypeScript isn’t happy about passing a FormData
type into the URLSearchParams
constructor.
ts
const
formData = newFormData ();const
Argument of type 'FormData' is not assignable to parameter of type 'string | string[][] | RecordqueryString = newURLSearchParams (). formData toString ();| URLSearchParams | undefined'. Type 'FormData' is missing the following properties from type 'URLSearchParams': size, sort 2345Argument of type 'FormData' is not assignable to parameter of type 'string | string[][] | Record| URLSearchParams | undefined'. Type 'FormData' is missing the following properties from type 'URLSearchParams': size, sort
But why is FormData
not allowed to be used with URLSearchParams
? MDN says it should work, it’s a common use case, what’s up?
The problem is that FormData
could also include non-string types such as a submitted File
. As always, TypeScript is correct and rightfully complaining — the FormData
object could hold data that URLSearchParams
can’t handle and this would lead to a nasty hard to find runtime exception.
So what’s the solution?
I could now iterate over the form data keys and type guard them, but because I know there won’t be a submitted file in my FormData
quick type casting did the trick for me.
ts
const
formData = newFormData ();const
searchParams = newURLSearchParams (
formData as unknown asRecord <string, string>,).
toString ();
If you want to find more possible solutions, check this related GitHub issue.