Ao trabalhar com a interface FormData em JavaScript, onde os dados são anexados como pares chave/valor, não há uma maneira interna de garantir a segurança de tipo nas chaves que você anexa. Isso pode levar a erros de digitação, chaves ausentes e erros inesperados em tempo de execução. Mas em TypeScript, podemos resolver isso ao impor uma validação rígida de chave.

Eu mesmo precisei dessa solução ao enviar os valores do meu formulário para uma API. Mais tarde, percebi que havia cometido vários erros de digitação em mais de um par chave/valor que estava tentando anexar à minha carga útil. Porque FormData aceita qualquer string como chave, eu pude passar as strings erradas e prosseguir com a solicitação à API.

Depois disso, procurei uma maneira de garantir que o TypeScript não permita esses erros.

Este artigo mostrará como tornar as chaves do FormData seguras em termos de tipo usando TypeScript.

Pré-requisitos

Para aproveitar ao máximo este artigo, você deve ter um entendimento básico do seguinte:

  1. programação em JavaScript

  2. fundamentos do TypeScript, especialmente como interfaces, tipos e o operador keyof funcionam

  3. a interface FormData

Se você é novo no TypeScript ou FormData, recomendo verificar a documentação oficial do TypeScript e o guia do MDN sobre FormData antes de prosseguir.

Passo 1: Definir suas chaves permitidas

O jeito antigo

O método padrão de anexar dados com FormData é fazê-lo manualmente, com strings simples:

const payload = new FormData();

payload.append("id", "1122");
payload.append("name", "Clark Kent");

payload.append("agge", "36"); // Erro de digitação na chave é permitido

No trecho de código acima, você pode ver que houve um erro de digitação ao definir uma chave para idade. Mas o TypeScript não irá sinalizá-lo como um erro, e isso poderia resultar em erros quando esses dados forem enviados com uma solicitação de API.

O jeito melhor

Em vez de digitar manualmente as chaves, defina-as em um esquema de objeto com uma interface TypeScript.

interface MyAllowedData {
    id: number;
    name: string;
    age: number;
}

Alternativamente, você pode defini-las com tipos:

type MyAllowedData = {
    id: number;
    name: string;
    age: number;
}

Você pode usar tipos ou interfaces, é apenas uma questão de preferência. Você pode descobrir mais sobre como eles diferem neste playground oficial de documentação do TypeScript.

Em seguida, defina um tipo de união para cada chave em sua interface.

type MyFormDataKeys = keyof MyAllowedData
// isto é o mesmo que `type MinhasChavesFormData = 'id' | 'nome' | 'idade'`

O operador keyof ajuda a criar um tipo de união das chaves de um tipo de objeto, sendo muito útil se você não quiser definir manualmente um tipo de união para um objeto maior com muitas chaves.

Passo 2: Criar uma Função Auxiliar de Anexação

Agora que você definiu suas chaves estritamente tipadas, o próximo passo é criar uma função auxiliar que garanta que apenas chaves válidas sejam anexadas ao FormData.

function appendToFormData (formData: FormData, key: MyFormDataKeys, value: string) {
  formData.append(key, value);
};

A função appendToFormData recebe três argumentos. Veja como tudo funciona:

  • O primeiro argumento, formData, é uma instância do objeto FormData. É aqui que os pares de chave/valor serão anexados antes de enviá-los em uma solicitação de API.

  • O segundo argumento, key, é o nome da chave do campo que você deseja anexar. Seu tipo é MyFormDataKeys, o tipo de união que criamos para garantir que apenas essas chaves definidas sejam anexadas ao FormData.

  • O terceiro argumento é uma string value que representa o valor a ser anexado com a chave.

Note que FormData só aceita string e Blob como valores em cada par chave/valor. Neste guia, estamos trabalhando apenas com valores de string – mas tenha em mente que você pode usar valores de blob para anexar arquivos a solicitações de API.

Agora, vamos testar a função:

const payload = new FormData();

appendToFormData(payload, "id", "19282"); // ✅ Permitido
appendToFormData(payload, "name", "Lenny Brown"); // ✅ Permitido
appendToFormData(payload, "age", "20"); // ✅ Permitido

appendToFormData(payload, "someOtherKey", "89"); // ❌ Erro TypeScript: O argumento do tipo 'someOtherKey' não é atribuível.

Passo 3: Use a Função Auxiliar após a Submissão do Formulário

Agora vamos anexar nossos campos ao FormData antes de enviá-los para uma API.

const handleSubmitForm = () => {
  const payload = new FormData();
   appendToFormData(payload, "id", "19282");
   appendToFormData(payload, "name", "Lenny Brown");
   appendToFormData(payload, "age", "20");

  // Enviar payload via API
  fetch("/api/submit", { method: "POST", body: payload });
};

Anexando Campos de um Objeto

Alternativamente, se você já tiver todo o seu payload em um objeto, você pode evitar anexar cada campo um por um implementando a função da seguinte forma:

const handleSubmitForm = () => {
  // todos os seus campos em um objeto
  const formValues: MyAllowedData = {
    id: 1123,
    name: 'John Doe',
    age: 56
  }
  const payload = new FormData();

  Object.entries(formValues).forEach(([key, value]) => {
    appendToFormData(payload, key as MyFormDataKeys, `${value}`); // use literais de modelo para passar o valor
  });

  // Enviar payload via API
  fetch("/api/submit", { method: "POST", body: payload });
};

No trecho acima, estamos usando Object.entries para iterar sobre cada par de chave/valor em um objeto, para que possa ser anexado ao objeto FormData. Observe que o valor em cada par, seja uma string ou um número, é passado como uma string usando template literals para evitar uma incompatibilidade de tipo do TypeScript do argumento value em nossa função auxiliar.

Conclusão

Aproveitando o operador keyof do TypeScript, podemos tornar o FormData.append() totalmente seguro em termos de tipos. Essa técnica simples ajuda a prevenir incompatibilidades de chave e torna suas solicitações de API mais confiáveis.

Deixe-me saber o que você achou do artigo e sinta-se à vontade para fazer qualquer sugestão que você ache que possa melhorar minha solução.

Obrigado por ler!