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 = new FormData();

const queryString = new URLSearchParams(formData).toString();

Argument 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, sort2345Argument 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 = new FormData();

const searchParams = new URLSearchParams(

formData as unknown as Record<string, string>,

).toString();

If you want to find more possible solutions, check this related GitHub issue.