Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[선물 등록, 선물방 편집] 리액트 쿼리 관련 데이터 실시간 업데이트 문제 해결 #339

Merged
merged 17 commits into from
Feb 16, 2024

Conversation

urjimyu
Copy link
Contributor

@urjimyu urjimyu commented Feb 10, 2024

이슈 넘버

구현 사항

선물등록 🎁

  • 불필요하게 설정되어 있던 mutationKey 값 제거
  • 선물 등록 완료 후 선물 등록 홈으로 이동했을 때 아이템이 자동 업데이트 되도록 수정

선물방 편집 ✍️

  • 선물방 입장 멤버 리스트가 뜨도록 수정
  • 개설자 프로필 이미지 뜨도록 수정
  • 삭제 시 자동 업데이트로 바로 수정된 리스트 확인되도록 수정

Need Review

1. 불필요하게 설정되어 있던 mutationKey 값 제거 및 수정된 데이터 자동으로 반영되도록 수정

  • mutationKey를 지정하지 않더라도 invalidateQueries를 활용하면 특정 쿼리를 무효화해서 해당 쿼리를 다시 부를 때 refetch가 이루어지도록 수정할 수 있어, useMutate 함수에 설정된 불필요한 mutationKey들을 모두 제거했습니다.

  • invalidateQueriesrefetchQueries 모두 실시간 업데이트를 위해 사용할 수 있으나, 불필요한 네트워크 호출을 줄이기 위해 invalidateQueries로 결정하였습니다. refetchQueries는 호출되는 즉시 refetch가 작동되는 반면, invalidateQueries는 쿼리 키 무효화만 우선 진행하기 때문입니다. 다만 invalidateQueries 사용 시 무효화할 쿼리에 대한 쿼리키를 지정할 때, 요소가 다르면 제대로 반영이 안 되므로 꼼꼼히 체크해야 합니다!

  • 선물홈, 선물방 편집에 쓰이는 delete 관련 서버 통신 코드를 모두 같은 방식으로 수정했습니다.

// useDeleteMyGift.tsx 일부
export function useDeleteMyGift(roomId: number) {
  const queryClient = useQueryClient();

  const mutation = useMutation({
    mutationFn: deleteMyGift,
    onSuccess() {
      console.log('선물 삭제 성공');
// 이 때 쓰이는 쿼리 키는 '내가 등록한 선물 리스트'를 GET 해오는 함수에서 지정해준 쿼리 키와 동일하게 지정해주어서
// 해당 쿼리에 대한 무효화가 진행되도록 합니다.
      queryClient.invalidateQueries({ queryKey: [MY_GIFT_QUERY_KEY[0], roomId] });
    },
    onError: (error) => {
      console.log('선물 삭제 중 에러가 발생했습니다.', error.message);
    },
  });
// useDeleteRoomMember.tsx
export const deleteRoomMember = async ({ roomId, memberId }: DeleteRoomMember) => {
  await del(`/room/${roomId}/members/${memberId}`);
};

const useDeleteRoomMember = ({ roomId }: DeleteRoomMember) => {
  const queryClient = useQueryClient();
  const mutation = useMutation({
    mutationFn: deleteRoomMember,
    onSuccess() {
      console.log('삭제 성공');
// 여기에서도 선물방 참여 멤버 리스트를 GET 해오는 쿼리를 무효화하기 위해 쿼리키를 아래와 같이 설정합니다.
      queryClient.invalidateQueries({ queryKey: [...ROOM_MEMBER_QUERY_KEY, roomId] });
    },
    onError: (error) => {
      console.log('선물 삭제 중 에러가 발생했습니다.', error.message);
    },
  });
  return { mutation };
};

2. onSuccess 중복 코드 삭제

  • 선물 등록 POST 성공 시 작동하는 onSuccess 코드가 컴포넌트와 서버통신 코드 모두에 중복되어 있어서 컴포넌트 내에 있는 부분 삭제했습니다
// AddGiftFooter.tsx
      if (openGraph.image) {
        mutation.mutate({
          roomId: itemInfo.roomId,
          name: name,
          cost: cost,
          imageUrl: imageUrl,
          url: link,
        });
      }

3. 선물방 편집 서버통신 관련 사소한 수정사항

  • 선물방 편집 화면에서 멤버 삭제 실시간 반영을 위해 사소한 수정사항 세 가지가 있었는데, 혹시 수정사항으로 인해 다른 뷰에 영향이 있을까봐 자세히 남깁니다!

    1. 선물방 참여 멤버 리스트가 뜨지 않는 이유가 map을 적용하는 roomMemberDataroomWholeMemberData.members로 되어 있었는데, 들어오는 데이터 형태과 다른 것 같아 const roomMemberData = roomWholeMemberData;로 수정했습니다.

    2. 선물방 개설자 프로필 이미지 변수명이 들어오는 데이터 타입과 이름이 살짝 달라 이미지가 안 뜨는 이슈가 있어 아래와 같이 수정했습니다

        export type OwnerListType = {
          ownerId: number;
          // 수정 전) profileImgUrl: string;
          profileImageUrl: string;
          name: string;
        };
    1. roomId 가져오는 방식을 변경했습니다. 쿼리 키 수정 이후 400 에러가 떠 확인해보니 roomId가 NaN으로 떴고, roomIdString이 null로 들어오는 것을 확인했습니다. 기존 방식이었던 useLocation()은 현재 주소에 쿼리 파라미터(물음표 있는 형식)가 있는 경우 location.search를 사용해서 파라미터 값을 가져오는 것인데, 저희의 주소값은 Path Variable형식이어서 주소값을 가져오고 있지 못했습니다. 따라서 useParams()를 이용해 roomId를 가져오는 방식으로 수정했습니다~
        const params = useParams();
        const roomIdString = params.roomId;

🐝 이번 PR의 리액트 쿼리 관련된 고민들에 대한 자세한 내용은 추후 아티클 작성 후 링크 첨부하겠습니다~!

📸 스크린샷

⬇️ 내가 등록한 선물 수정된 화면

Screen.Recording.2024-02-10.at.8.30.36.PM.mov

⬇️ 선물방 편집 수정된 화면

  • 전체 선물방 참여자 수는 DB에 직접 인원 추가한 상태라 안 바뀌는 거라서 무시해도 됩니다!
Screen.Recording.2024-02-10.at.7.02.40.PM.mov

Reference

Path Variable vs Query Parameter

@urjimyu urjimyu added fix 🛠️ Something isn't working feat💡 기능 구현 선물등록 🎁 선물 등록 페이지 API 🧬 api 연결 선물방 편집 ✍️ labels Feb 10, 2024
@urjimyu urjimyu self-assigned this Feb 10, 2024
Copy link

github-actions bot commented Feb 10, 2024

PR Preview Action v1.4.6
🚀 Deployed preview to https://SWEET-DEVELOPERS.github.io/sweet-client/pr-preview/pr-339/
on branch gh-pages at 2024-02-16 15:29 UTC

@hoeun0723
Copy link
Contributor

hoeun0723 commented Feb 13, 2024

안녕하세요 !!
변경사항이 선물방 수정 부분이어서 혹시나 안 되는 부분이나 수정사항이 필요할까봐 적어주신 pr 내용 꼼꼼하게 읽어보았습니다 !! 상세하게 작성해주셔서 감사해요!

  1. OwnerListType에서 profileImgUrl 을 profileImageUrl 로 바꾸어 잘 안 들어오고 있는 문제를 해결해주셨는데, EditRoom.tsx에서 return 문 속에서 프로필 이미지를 불러오는 부분이 CardGuest 컴포넌트 속에 있는데, 해당 부분도 profileImageUrl 로 수정이 필요하겠다고 생각했는데 이미 반영해주셨군요 !!! 너무 감사합니다 ♥️

  2. useParams로 수정한 부분 외에 useLocation이 아직 남아있었나보네요!! 확인해보니, TouernamentRankingTitle, GiftHome등등이 있었네요
    꼼꼼한 확인 후 수정 감사합니다 😊

빠르게 반영 되는 영상 화면을 보니 속이 편안하네요 ㅎㅎㅎ 수정 및 반영 너무 감사드립니다 !!! 저도 덕분에 react-query 다시 한번 더 코드 볼 수 있게 됐어요 !!! 😊

Copy link
Contributor

@hoeun0723 hoeun0723 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

콘솔이나 주석 등 간단한 리뷰 남겼습니다 !

  • 화살표 함수가 선언식으로 변경 된 부분에 대해서도 남겨서 해당 부분 답글 부탁드립니다 !

Comment on lines 42 to 45
function onClickBtn() {
//빌드 에러 해결용 콘솔
console.log(fileName);

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

console.log 부분도 해결이 완료 됐다면 한번 싹 지우는것도 좋을 거 같아요 !!! 콘솔은 이 부분만 코리 남기겠습니다 😊

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

리마인드 감사합니다~!!

Comment on lines +52 to +58
mutation.mutate({
roomId: itemInfo.roomId,
name: name,
cost: cost,
imageUrl: imageUrl,
url: link,
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

흠.. 뭔가 이 객체를 따로 관리 할 수 있는 방법이 없을까요 ??? 상수화 하여 가독성이 좋아지도록이요!! 이렇게 직접적으로 드러내는게 더 좋은 것 같다면 그것도 오케이인데, if 부분과 else 부분 모두 이 코드가 중복 되어지는거 같아서요!!

Copy link
Contributor Author

@urjimyu urjimyu Feb 16, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

조건문의 경우 presignedUrl을 막으면서 임시로 추가된 것이어서 다른 이슈 해결하면서 중복 제거했습니다! 말씀해주신 대로 상수화를 통해 가독성을 개선하면 훨씬 좋을 것 같네요 감사합니다🥰 context 브랜치에서 상수화 가능할 것 같아서 다른 PR에서 적용해보겠습니다:)

onClick: () => void;
onClick: VoidFunction;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

오오 !!!! 매우매우 굳입니다 ㅎㅎㅎ 이거 그때 합숙때 알게 된 것이군요 !! 저도 반영해야겠어요😊

const [isAdVisible, setIsAdVisible] = useState(myGiftData.length <= 1);
// const [isAdVisible, setIsAdVisible] = useState(myGiftData.length <= 1);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

주석 부분 삭제해도 좋을 거 같아요 ! 😊

export const useDeleteMyGift = (roomId: number) => {
export function useDeleteMyGift(roomId: number) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

화살표 함수가 아닌 선언식의 형태로 함수를 작성한 이유가 궁금합니다 !!
초반 컨벤션을 정할때 화살표 함수로 지정했던 거 같아서요 !! react-query에선 특별한 이유가 있나요 ~??
다른 부분에도 모두 동일하게 변동 되어 있길래, 해당 부분에만 리뷰 남깁니다 !

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이것은 그저 저의 착각으로 인한 실수입니다!ㅠㅠ 작업하다가 갑자기 컨벤션 착각해서 막 고쳤는데 알려주셔서 감사합니다✨

Comment on lines 17 to 26
// 주소가 path variable 형태이기 때문에 location.search로 쿼리 파라미터를 가져올 수 없어
// 룸아이디가 null로 받아지고 있었고, 이로 인해 400에러가 발생해서 임의로 수정했습니다
// const location = useLocation();
// const searchParams = new URLSearchParams(location.search);

// const roomIdString = searchParams.get('roomId');

const params = useParams();
const roomIdString = params.roomId;

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pr 내용 봤습니다 !! : ) 해당 부분 주석 삭제해주셔도 될거 같아요 !! ㅎㅎ

Copy link
Member

@imeureka imeureka left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

대박 너무 깔끔한 정리와 pr내용 덕분에 쿼리 클라이언트의 invalidateQueries의 선택 기준에 대해서 다시 알게 되었네요 🥰
너무 고생많으셨어요! 왜 query와 mutation을 따로 나눴을까? 궁금해져서 강의를 듣고 찾아보니,

왜 query와 mutation을 따로 나눈 이유는 mutation은 사이드 이펙트를 일으키는 방법이고, 반면, 데이터를 조회하는 것은 사이드 이펙트를 발생시키지 않습니당. React Query는 이 둘의 개념을 나누고, 사용하는 방법도 다르게 구성해두었더라고요!

"query는 선언적으로 사용할 수 있으며, mutation은 명령형으로 밖에 사용하지 않도록 말이다."
그렇기에 query는 마운트 될 때 즉시 실행되어 데이터를 조회합니다.
하지만 mutation은 서버 데이터를 변형시키는 것이기 때문에, 마운트 될 때 실행되면 안되죠! 그래서 useMutation 훅을 사용해 mutation 객체를 가져오고, 명령적으로 필요할 때 호출하도록 하고 있다고 합니다..! 대박,,너무 재밌네요🧐

그래서 제가 카공하다 지민님께 드린 질문 "mutation은 서버 데이터를 변경하는건가..?" 에 대한 정확한 답변으론
: mutation이 실행된 이후 서버의 데이터는 변경되었지만, React Query의 mutation 실행 이후 클라이언트의 서버 데이터는 변경 되지 않은 상태라 이해하면 될 것 같아요. 즉 클라이언트의 서버 데이터라함은, query로 조회해온 서버 데이터가 클라이언트의 상태로 넘어온 것이니까 저희가 mutation 이후 어떻게 화면에 데이터가 업데이트 되는지를 따로 설정해줘야 했고 지민님은 invalidateQureis로 업데이트 되게 해준 것 같네요!

관련 좋은 글도 공유해드립니다. 정말 이해가 쏙쏙 되니 꼭 읽어보세요!
React Query에서 mutation 이후 데이터를 업데이트하는 4가지 방법

const queryClient = useQueryClient();

const mutation = useMutation({
mutationKey: [MY_GIFT_QUERY_KEY[0]],
mutationFn: deleteMyGift,
onSuccess() {
console.log('선물 삭제 성공');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mutationKey 제거 대박 너무 좋아요!
제가 최근에 강의를 봤는데 사용자에게 변경사항을 실제로 보여줄 땐 3가지 방법이 있다합니다!

  1. 낙관적인 업데이트 : 서버 호출이 잘될 거라 가정하고, 잘 안됐을 경우 되돌리는 방식
  2. 서버에서 받은 데이터를 가져오는 겁니다 : mutation을 실행 할 때 업데이트 된 데이터를 가져와 리액트 쿼리 캐시를 업데이트하는 방식
  3. 마지막으로 관련 쿼리를 무효화하는 방식 : 쿼리를 무효화하면, 클라 데이터를 서버의 데이터와 동기화하기 위해 서버에 재요청이 발생하는 방식
스크린샷 2024-02-14 오후 4 20 44 그래서 언니 방식이 3번째 방식으로 진행되었구나! 바로 이해가 쏘옥 되었습니다. 혹시 다른 분들을 위해 리뷰 남겨봅니다.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

대박.. 이 리뷰 너무너무 좋아요 이렇게 3가지 방식이 있고, 저희는 3번째 방식으로 진행하고 있다는 사실이 이해가 쏙쏙 된거 같습니다 !!!!! 너무너무 감사해요 너무너무 좋아요 !!!!!!!!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

와,, 정말 리액트 쿼리 설명 너무 이해 쏙쏙 되게 깊이 있게 해주셔서 감사해요!! 덕분에 더 깊은 내용까지 이해하게 되었습니다🫶 좋은 레퍼런스까지,, 정말 감사해요!

const mutation = useMutation({
mutationFn: postNewGift,
onSuccess: () => {
console.log('roomId, targetDate', roomId, targetDate);
console.log('선물 등록 성공!!');
queryClient.invalidateQueries({ queryKey: [MY_GIFT_QUERY_KEY[0], roomId] });
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

아! 혹시 쿼리클라이언트의 setQueryData 가 있던데 invalidateQuries를 사용한 이유가 궁금해서 또 찾아보니
setQueryData : 낙관적 업데이트(위에서 언급한 1번)에 가장 적합하거나 서버 상태에 영향을 주지 않고 일시적으로 캐시를 재정의해야 하는 경우에 가장 적합하다 하네요!
invalidateQueries : 변형이나 기타 중요한 변경 후 클라이언트 캐시가 서버의 현재 상태를 반영하도록 하는 데 더 적합하다하여 아항 이럴경우 invalidateQureis를 사용해야함을 깨달았습니다..! 네이밍이 뭔가 setQueryData도 될 것 같아서 찾아보았습니다 ! ㅎㅎ
tanstack쿼리 클라이언트 공식문서

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이렇게나 섬세한 리뷰 너무나 소중하네요❣️ setQueryData에 대해서는 제대로 알지 못했는데 말씀해주신 대로 더 적절할 것 같네요! 수정해보겠습니다:)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

리뷰해주신 부분 참고해서 POST 관련된 부분들은 setQueryData로 수정했습니다 감사합니다!

Copy link
Contributor Author

@urjimyu urjimyu Feb 18, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

클라이언트 캐시가 선물이 등록된 상태의 서버 상태를 반영해야 하는 상황이라 2번이 더 적합한 것 같더라고요..! setQueryData는 실시간 업데이트가 안 되는 문제 사항을 발견해서 모두 invalidateQueries로 돌려놓았습니다🥲 리액트 쿼리의 데이터 업데이트 방법에 대해 더 공부해보겠습니다!

@urjimyu urjimyu merged commit 66e6c90 into develop Feb 16, 2024
1 check failed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
API 🧬 api 연결 feat💡 기능 구현 fix 🛠️ Something isn't working 선물등록 🎁 선물 등록 페이지 선물방 편집 ✍️
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants