skip to Main Content

I have a form data which consists of multiple images under the same product id.

import client from "@/lib/fetchWrapper";

export default async function addProductImage({
  photo,
  productId,
}: {
  photo: File[];
  productId: string;
}) {
  if (!productId || typeof productId !== "string") {
    throw new Error("productId must be a non-empty string.");
  }

  try {
    const formData = new FormData();
    formData.append("productId", productId);

    if (!photo || photo.length === 0) {
      throw new Error("No image files found.");
    }

    // Append each image to the FormData
    for (let i = 0; i < files.length; i++) {
      formData.append('images[]', file[i])
}
    formData.forEach((value, key) => {
      console.log(key, value);
    });
    
    const formDataObj = Object.fromEntries(formData.entries());
    console.log(formDataObj)


    const response = await client("v1/product-image", {
      method: "POST",
      body: formDataObj,
       headers: {
      'Content-Type': 'application/json',
    },
    });

    return response;
  } catch (error) {
    console.error("Error uploading product images:", error);
    throw error;
  }
}

I tried uploading three images and all three images are displayed while consoling the formdata:

productId 5b1a763a-4867-4bd8-a47b-f6d1f4642863
addProductImage.ts:55 images[] 
File {name: 'durga-puja-parvati-ganesha-hinduism-png-favpng-aFuv1A72aKarGGLjPzqwaBKy1_t.png', 
lastModified: 1697095379428, 
lastModifiedDate: Thu Oct 12 2023 13:07:59 GMT+0545 (Nepal Time),
 webkitRelativePath: '', size: 48632, …}lastModified: 1697095379428lastModifiedDate: Thu Oct 12 2023 13:07:59 GMT+0545 (Nepal Time) {}name: "durga-puja-parvati-ganesha-hinduism-png-favpng-aFuv1A72aKarGGLjPzqwaBKy1_t.png"size: 48632type: "image/png"webkitRelativePath: ""[[Prototype]]: File
addProductImage.ts:55 images[] File {name: 'durga-puja-parvati-ganesha-hinduism-png-favpng-aFuv1A72aKarGGLjPzqwaBKy1_t.jpeg', lastModified: 1697090823881, lastModifiedDate: Thu Oct 12 2023 11:52:03 GMT+0545 (Nepal Time), webkitRelativePath: '', size: 15891, …}
addProductImage.ts:55 images[] File {name: 'drug copy.png', lastModified: 1687005222158, lastModifiedDate: Sat Jun 17 2023 18:18:42 GMT+0545 (Nepal Time), webkitRelativePath: '', size: 45617, …}
addProductImage.ts:59 

But when I convert the the formdata into JavaScript object using const formDataObj = Object.fromEntries(formData.entries());

Only, productId is displayed in the payload not the array of images.

The fetchwrapper code:

export default function client(
  endpoint: string,
  { body, ...customConfig }: any = {}
) {
  const token =
    localStorage.getItem("ecommerceToken") ||
    sessionStorage.getItem("ecommerceToken");
  const headers = { "content-type": "application/json", Authorization: "" };
  if (token) {
    headers.Authorization = Bearer ${token};
  }
  const config = {
    method: body ? "POST" : "GET",
    ...customConfig,
    headers: {
      ...headers,
      ...customConfig.headers,
    },
  };
  if (body) {
    config.body = JSON.stringify(body);
  }

  return fetch(${process.env.NEXT_PUBLIC_API_URL}/${endpoint}, config).then(
    async (response) => {
      if (response.ok) {
        return await response.json();
      } else {
        const error = await response.json();
        return Promise.reject(new Error(error?.message));
      }
    }
  );
}

The error on the response part is:

{
    "statusCode": 400,
    "message": [
        "productId must be a string",
        "productId should not be empty"
    ],
    "error": "Bad Request"
}

But, I still see a valid productId in the payload.

2

Answers


  1. This is probably a duplicate but the answers I found when searching ranged from incomplete to terrible, so here we go:

    You cannot naively convert a FormData using Object.fromEntries because FormData can hold multiple entries for a given key but JS objects are LWW (Last Write Wins) for a given key. For example:

    const foo = new FormData();
    foo.append('bar', 1);
    foo.append('bar', 2);
    const obj = Object.fromEntries(foo);
    console.log(obj); // { bar: 2 }
    foo.forEach(console.log); // bar: 1, bar: 2!
    

    Note that you’re appending the same key images[] over and over in your loop.

    Here’s a general purpose conversion function that will turn multiple entries at the same key into an array:

    const formDataToObj = (fd) => {
      const result = {};
      fd.forEach((val, key) => {
        // NOTE: FormData converts all values
        // to strings, this will convert e.g.
        // numbers and bools back
        let value = val;
        try {
          value = JSON.parse(value);
        } catch (err) {
          // pass
        }
    
        if (key in result) {
          if (Array.isArray(result[key])) {
            result[key].push(value);
          } else {
            result[key] = [result[key], value];
          }
        } else {
          result[key] = value;
        }
      });
    
      return result;
    };
    
    Login or Signup to reply.
  2. Mine is similar to Jared but I think JSON.parse is unnecessary since you have to convert it back to JSON string so it can be sent in http request.
    Also JSON.parse cab be expensive to compute if you have large/nested object.

    /** @param {FormData} formdata */
    const convertFormDataToObject = (formdata) => {
    
      /** @type {Record<string, unknown>} **/
      const resultObj = Object.create(null) // prevent prototype pollution
    
      for (const [key, fvalue] of formdata) {
        let existingValue = resultObj[key]
        if (existingValue) {
          if (Array.isArray(existingValue)) existingValue.push(fvalue)
          else resultObj[key] = [existingValue, fvalue]
        } else {
          resultObj[key] = fvalue
        }
      }
    
      return resultObj
    }
    
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search