Published at
Updated at
Reading time
2min

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.

// what I wanted to do ...
export async function action({
  request,
}) {
  const formData = await request.formData();
  const queryString = new URLSearchParams(formData).toString();
  
  // make another API call with the constructed query string
  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<string, string> | 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<string, string> | 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.

If you enjoyed this article...

Join 5.5k readers and learn something new every week with Web Weekly.

Web Weekly — Your friendly Web Dev newsletter
Reply to this post and share your thoughts via good old email.
Stefan standing in the park in front of a green background

About Stefan Judis

Frontend nerd with over ten years of experience, freelance dev, "Today I Learned" blogger, conference speaker, and Open Source maintainer.

Related Topics

Related Articles