skip to Main Content

I’ve these 3 fields in my form (sku, sku_variation, name) and I want to use them to create a new product.

I came up with this solution recasting the parsedData to unknown first but it looks completely bad practice.

  export type TProduct = {
    id: string,
    sku: number,
    skuVariation: number,
    name: string,
  }

  export type TCreateProduct = Omit<TProduct, 'id'>;

  const createProduct = async(parsedData:TCreateProduct) => {
    fetch('http://localhost:3333/api/products/',{
      method: 'POST',
      body: JSON.stringify(parsedData)
    })
    return '';
  }

  const handleSubmit = (e:React.FormEvent<HTMLFormElement>) => {
    e.preventDefault();

    const form = new FormData(e.target as HTMLFormElement);
    const parsedData = Object.fromEntries(form.entries()) as unknown as TCreateProduct;
    createProduct(parsedData);
  }

Without the as unknown as TCreateProduct casting the error thrown in the createProduct call as:

Argument of type '{ [k: string]: FormDataEntryValue; }' is not assignable to parameter of type 'TCreateProduct'.
  Type '{ [k: string]: FormDataEntryValue; }' is missing the following properties from type 'TCreateProduct': sku, skuVariation, name

2

Answers


  1. TypeScript is right to force you to do the hacky double cast as your cast is invalid. FormData is only going to have values of type string or File, but the TCreateProduct type has properties with number values. This means that if a key matches one of those properties with a number value then the type of the property in the resulting object would be incorrect.

    If you change your type to be a record of strings, then you can remove the extra cast to unknown. Of course, you’ll probably have an issue with createProduct since the request body be serialized with values for sku and skuVariation encoded as strings.

    export type TCreateProduct = Record<Exclude<keyof TProduct, 'id'>, string>;
    
    const handleSubmit = (e:React.FormEvent<HTMLFormElement>) => {
      e.preventDefault();
    
      const form = new FormData(e.target as HTMLFormElement);
      const parsedData = Object.fromEntries(form.entries()) as TCreateProduct;
      createProduct(parsedData);
    }
    

    Alternatively you can map the values, doing the appropriate transformations for number values. It won’t look as neat as doing Object.fromEntries(), but it’ll be the most accurate.

    export type TCreateProduct = Omit<TProduct, 'id'>;
    const handleSubmit = (e:React.FormEvent<HTMLFormElement>) => {
      e.preventDefault();
    
      const form = new FormData(e.target as HTMLFormElement);
      const parsedData: TCreateProduct = {
        name: form.get('name') as string,
        sku: parseInt(form.get('sku') as string),
        skuVariation:  parseInt(form.get('sku') as string)
      };
      createProduct(parsedData);
    }
    
    Login or Signup to reply.
  2. You can use a third-party library to act as a TypeGuard and values validator. There are some, like Joy and Zod. I will give you an example with Zod

    
    
    const createProductSchema = z.object({
      sku: z.number(),
      skuVariation: z.number(),
      name: z.string().min(3),
    });
    
    const productSchema = createProductSchema.extend({
      id: z.number(),
    });
    
    export type TProduct = z.infer<typeof productSchema>;
    
    export type TCreateProduct = z.infer<typeof createProductSchema>;
    
    const createProduct = async (parsedData: TCreateProduct) => {
      fetch('http://localhost:3333/api/products/', {
        method: 'POST',
        body: JSON.stringify(parsedData),
      });
      return '';
    };
    
    const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
      e.preventDefault();
    
      const form = new FormData(e.target as HTMLFormElement);
      const parsedData = createProductSchema.parse(
        Object.fromEntries(form.entries())
      );
      createProduct(parsedData);
    };
    
    
    

    Have in mind that createProductSchema.parse will throw an error if data is not correct

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search