๐Ÿ‘พ antd๋ฅผ ํ™œ์šฉํ•œ upload component์˜ api request์™€ response์˜ ํƒ€์ž… ์—๋Ÿฌ

Summary

antd๋ฅผ ํ™œ์šฉํ•ด upload component๋ฅผ ๋งŒ๋“œ๋Š” ๊ณผ์ •์—์„œ api request์™€ response์˜ ํƒ€์ž… ์—๋Ÿฌ๊ฐ€ ๊ณ„์†ํ•ด์„œ ๋ฐœ์ƒํ•˜์˜€์Œ. form์„ ํ†ตํ•ด ์ œ์ถœํ•ด์•ผํ•˜๋Š” api response ํƒ€์ž…๊ณผ ์ง์ ‘ antd๋ฅผ ํ†ตํ•ด set๋˜๋Š” value์˜ ํƒ€์ž…์ด ๋‹ฌ๋ผ ๋ฌธ์ œ๊ฐ€ ์ƒ๊น€. http 501, http 400 ์—๋Ÿฌ๊ฐ€ ๊ฐ™์ด ์ƒ๊ฒจ๋‚จ

Supporting data

  • formData
    • formData๋Š” ๊ทœ์ •์ƒ ์›๋ž˜ ์ฝ˜์†”์—์„œ๋Š” ํ™•์ธ์ด ๋ถˆ๊ฐ€(network ์˜ payload๋„ ๋งˆ์ฐฌ๊ฐ€์ง€)
  • file upload ๋กœ์ง
    • formData๋ฅผ file api๋กœ ์ „๋‹ฌํ•˜๊ณ  ํŒŒ์ผ url๋ณ€ํ™˜
    • url์„ post ๋ฐฐ์—ด์— ๋‹ด์•„ ์ฒ˜๋ฆฌ

Incident Response Analysis

api์˜ ํ˜•์‹์— ๋งž๋Š” ๋ฐฐ์—ด ๋”ฐ๋กœ, antd์˜ ํ˜•์‹์— ๋งž๋Š” ๋ฐฐ์—ด ๋”ฐ๋กœ ์ƒ์„ฑ 1. api ํ˜•์‹์— ๋งž๋Š” ๋ฐฐ์—ด ๋”ฐ๋กœ ๋งŒ๋“ค๊ณ  api์— PUT์‹œํ‚ค๊ธฐ 2. antd์˜ ํ˜•์‹์œผ๋กœ ๋ณ€ํ™˜ํ•œ ๋ฐฐ์—ด์„ ๋”ฐ๋กœ ๋งŒ๋“ค์–ด ํ™”๋ฉด์ƒ์˜ ๋ Œ๋”๋ง์ „์šฉ(?)์œผ๋กœ ์‚ฌ์šฉํ•˜๊ธฐ -> ํƒ€์ž…์—๋Ÿฌ ๋ฐœ์ƒํ•˜์ง€ X

-> ์‹ค์ œ ์ฝ”๋“œ

  • form.getFieldValue()๋ฅผ ํ†ตํ•ด ๊ธฐ์กด์˜ default๊ฐ’ ํ• ๋‹น
  • ํŒŒ์ผ ์—…๋กœ๋“œ ์ „, beforUpload() ๋ฅผ ํ†ตํ•ด ๊ฐ๊ฐ์˜ ํŒŒ์ผ์„ย FormData๋กœ ๋ณ€ํ™˜ํ•˜๊ณ  uploadFileList์— ์ถ”๊ฐ€
  • form.setFieldValue()๋ฅผ ํ†ตํ•ด form์— uploadFileList๋ฅผ ์ง์ ‘ ํ• ๋‹น
const [uploadFileList, setUploadFileList] = useState<string[]>(
  form.getFieldValue(name) || []
);
const convertedFileList: UploadFile[] = uploadFileList?.map(convertUploadFile);

const handleUpload = (file: RcFile) => {
  const image = new FormData();
  image.append("multipartFile", file);

  uploadFile({ domain, image })
    .unwrap()
    .then((value) => {
      setUploadFileList([...uploadFileList, `https://${value.file_url}`]);
      form.setFieldValue(name, [
        ...uploadFileList,
        `https://${value.file_url}`,
      ]);
      message.success("์—…๋กœ๋“œ์— ์„ฑ๊ณตํ–ˆ์Šต๋‹ˆ๋‹ค.");
    })
    .catch(() => {
      message.error("์—…๋กœ๋“œ์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค.");
    });
  return true;
};
  • convertUploadFile()ย hook์„ ํ†ตํ•ด antdํ˜•์‹์— ๋งž๊ฒŒ ๋ณ€ํ™˜
const convertUploadFile = (fileUrl: string, index: number): UploadFile => {
  return {
    uid: `${-(index + 1)}`,
    name: fileUrl,
    status: "done",
    url: fileUrl,
  };
};

Post-Incident Analysis

  • TypeError๋ฌธ์ œ
    • api์— ์ „์ฒด ๋ฐ์ดํ„ฐ๋ฅผ putํ• ๋•Œ์˜ image_url์˜ type์€ string
    • antd๋ฅผ ํ†ตํ•ด ์‹ค์ œ ๋‹ด๊ธฐ๋Š” filel์˜ type์€ antd์ „์šฉ ๊ฐ์ฒด์ธ uploadFile
    • image_url์„ getํ• ๋•Œ๋Š” string[] ์ด๋ฏ€๋กœ fileList๋ฅผ ๋ Œ๋”๋งํ• ๋•Œ์— antd๊ฐ์ฒด๋กœ ๋ฐ”๊พธ์–ด์•ผํ•จ.
    • ์ด์™€๊ฐ™์€ api ์— GET, PUT ํ• ๋•Œ ์„œ๋กœ ๋‹ค๋ฅธ data type์œผ๋กœ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒ
  • ํŒŒ์ผ ์—…๋กœ๋“œ ์ „์šฉ api ์— ๋Œ€ํ•œ ์ดํ•ด ๋ถ€์กฑ
    • ํŒŒ์ผ ์—…๋กœ๋“œ api์— requst๋ฐฉ๋ฒ•๊ณผ response๊ฐ€ ๋ฌด์—‡์ธ์ง€ ์ œ๋Œ€๋กœ ์ธ์ง€ํ•˜์ง€ ๋ชปํ–ˆ์Œ.
    • ํŒŒ์ผ ์—…๋กœ๋“œ api๋Š” formData๋ฅผ ๋ฐ›์•„ file์˜ ์ „์šฉurl์„ ๋ณ€ํ™˜ํ•ด์ฃผ๋Š” ์—ญํ• ์„ ํ•จ.

Timeline

240329-001031

Lessons Learned:

  • ์—๋Ÿฌ ์ƒํƒœ ํ™•์ธ
  • api response, request type ํ™•์ธ
  • ์ด์šฉํ•˜๋Š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ parameter ๊ณต์‹๋ฌธ์„œ ํ™•์ธํ•˜๊ธฐ

Lessons Learned

  • ์—๋Ÿฌ ์ƒํƒœ ํ™•์ธ
  • api response, request type ํ™•์ธ
  • ์ด์šฉํ•˜๋Š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ parameter ๊ณต์‹๋ฌธ์„œ ํ™•์ธํ•˜๊ธฐ