본문 바로가기
Front-End Study/이건 우째해야 할까..

PATCH 메서드를 사용한 수정 로직 구현... 이건 우째 해야할까..

by 코딩기 2024. 7. 14.
728x90

어떤 고민?

✨ 이번 프로젝트 때 위키 수정을 위해 사용하는 PATCH 메서드를 위해 FormData 생성로직을 만들어야 했다.

 

🔖 초기 로직과 문제점


🚫 로직의 비효율성

  • 우리 API는 PATCH 메서드를 통해 위키를 수정하는데, 수정 전에 퀴즈를 풀어 답변과 함께 엔드포인트가 ping인 API로 GET 요청을 보내어 성공 상태 코드를 받아야 PATCH 메서드를 사용 가능한 시스템이었다. (따로 알려주지 않아 직접 swagger로 박치기해서 알아냈다... 🥲)
  • 여하튼 이러한 과정은 다 제쳐두고 PATCH 메서드 자체만 놓고 보자면, PATCH의 경우 변경하고 싶은 데이터만 뽑아내어 FormData에 담아 전송해야 했기 때문에 이걸 어떤식으로 만들어야 하나라는 고민이 많았다.
  • 또한 이미지를 넣어 보내는 경우, image 엔드포인트 API에 먼저 보내어 알맞는 URL을 획득하여 다시 넣어줘야 하는 번거로움도 있었다.

 

📖 처음 생각했던 로직

// formData 상태값
const [formData, setFormData] = useState<ChangeProfilesFormData>(FORM_DATA_INIT);

// 데이터 전송 로직
const handleSaveClick = async () => {
  try {
    const data = new FormData();

    // 이미지 처리 함수
    const processImage = async () => {
      if (formData.image) {
        const imageData = new FormData();
        imageData.append('image', formData.image);
        const res = await getImageUrl(imageData as ImageData);
        return res?.url || '';
      }
      return 'null'; // 이미지가 없을 경우 'null' 문자열 전송
    };

    // 변경된 필드와 데이터 처리
    Object.keys(formData).forEach((key) => {
      const currentValue = formData[key as keyof ChangeProfilesFormData];
      const previousValue = FORM_DATA_INIT[key as keyof ChangeProfilesFormData];

      if (currentValue !== previousValue && key !== 'image') {
        data.append(key, currentValue as string);
      }
    });

    // 이미지 처리 후 URL 추가
    const imageUrl = await processImage();
    data.append('image', imageUrl);

    // 프로필 변경 요청
    const res = await changeProfile(userProfile?.code, data as unknown as ChangeProfilesFormData);
    setUserProfile(res);
    setIsEditing(false);
    resetState();
  } catch (error) {
    alert(error);
  }
};
  • 처음 생각했던 로직과 해당 로직을 작성할 때 내 생각은 이러했다.
    1. PATCH 메서드를 보내기 위한 FormData 에는 변경하고 싶은 데이터만 들어가야 한다. (기본적으로 '수정'이란 내가 원하는 데이터를 변경하되 남은 데이터는 그대로 두어야 하므로)
    2. JS는 기본적으로 API 요청 시 보내는 데이터 객체로 new FormData() 를 제공한다.
    3. 따라서 FORM_DATA_INIT 이라는 기본 객체 템플릿을 만들고, 이를 formData 상태값의 기본값으로 설정 후, 데이터를 보낼 때마다 두 객체의 value를 비교하여 변경된 것만 FormData 객체에 넣는다면 이를 구현할 수 있지 않을까?

 

🥲 그땐 좋다고 생각했으나 지금 생각해보면 상당히 비효율적이고 엉성하다.

  • 또한 useState 를 사용하는 로직은 기본적으로 비동기이고, new FormData() 로직을 활용하는 건 분기를 설정하기에도 애매한 점이 있었다.
  • 또한 가장 중요했던 점은, 수정 모드로 진입 시 기존 데이터를 모두 수정할 FormData 에 기본 값으로 설정하여 변경하고 싶은 값만 변경하면서도 데이터의 상태를 확인할 수 있도록 의도했는데, 상태값이다 보니 변경하다가 취소할 시 페이지를 새로고침하지 않는 이상 그대로 데이터가 남아버린다는 점이었다.
  • 또한 이미지와 같은 따로 데이터를 보내고 받아와야 하는 로직도 포함되어 있다보니 데이터가 엉키는 느낌이어서 사용할 수록 이 로직은 아니라는 생각이 강하게 들었다.

 

📖 다시 시작된 고민과 해결

  • 사실 이 로직을 고민하기 전에 그냥 formData 상태값을 직접 보내는건 안되는건가? 라는 생각도 해보았었다.
  • 그런데 Content-Type을 따로 설정해주지 않았었고 지식도 별로 없었어서 무조건 new FormData()를 통해 객체를 만들어야지만 가능한건가보다.. 하고 말았었는데 😂
  • 😎 멘토님께서 그냥 formData 상태값을 직접 보내는게 나을거라고 조언해주셨다..!
  • 또한 PATCH는 결국 변경을 위해 데이터를 뽑아 보내는 것이므로, 변경할 값을 제외한 다른 모든 값을 그대로 보내면 결국 변경하고 싶은 값만 바뀌는거나 다름없다는 말씀도 해주셨다.
  • 고 말을 들으니까 눈이 번쩍 뜨이고 답답했던 속이 시원해지는 느낌을 받았다..! 🧊🧊🧊
// formData 전송 로직
const handleSaveClick = async () => {
  // 로직 변경 당시 추가됐던 확인 모달 off 처리 함수
  confirmModalOff();
  try {
    // 기존 formData를 업데이트하는 객체
    let updatedFormData = { ...formData };

    // 이미지가 있을 시 따로 URL을 가져오는 함수
    if (formData.image instanceof File) {
      const imageData = new FormData();
      imageData.append('image', formData.image);

      const res = await getImageUrl(imageData as ImageData);
      if (res?.url) {
        updatedFormData = {
          ...formData,
          image: res.url,
        };
        setFormData(updatedFormData);
      }
    }

    // 최종 데이터 전송
    const profileUpdateResponse = await updateProfile(
      userProfile?.code,
      updatedFormData as ChangeProfilesFormData,
    );

    setUserProfile(profileUpdateResponse);
    setIsEditing(false);
  } catch (error: unknown) {
    if (isAxiosError(error)) {
      ToastSelect({ type: 'error', message: error?.response?.data.message });
    } else {
      ToastSelect({ type: 'error', message: '예상치 못한 에러가 발생했습니다.' });
    }
  } finally {
    setRenewalTime(!renewalTime);
  }
};

// 초기 상태값 세팅
const updateFormData = useCallback(() => {
  if (userProfile) {
    const {
      nationality,
      family,
      bloodType,
      nickname,
      birthday,
      sns,
      job,
      mbti,
      city,
      image,
      content,
    } = userProfile;

    const newFormData: ChangeProfilesFormData = {
      nationality,
      family,
      bloodType,
      nickname,
      birthday,
      sns,
      job,
      mbti,
      city,
      image,
      content,
    };

    setFormData(newFormData);
  }
}, [userProfile]);
  • 조언받은대로 로직을 변경해보았다.
  • 위 로직은 받아온 userProfile 객체를 바탕으로 원하는 키만 뽑아내고 이를 formData 상태값에 넣어주는 방식이며, 이를 이용해 PATCH 전송 로직을 구성하였다.
  • 이렇게 변경함으로서 handleSaveClick이 수행하는 작업의 수를 훨씬 줄일 수 있었고, 새로고침하지 않더라도 상태값이 변경된 상태로 유지되는 문제 또한 해결할 수 있었다.

 

🏷️ 추가적으로 고민해볼 사항

  • 일단 이정도로 마무리를 지었지만, 여전히 코드를 좀 더 간소화 시킬 수 있지 않을까? 라는 아쉬움이 남는다.. 시간이 날 때마다 조금씩 고민해봐야겠다고 생각했다.

우째하긴~ 이렇게 하믄 된다! 👍