Loading...
본문 바로가기
👥
총 방문자
📖
0개 이상
총 포스팅
🧑
오늘 방문자 수
📅
0일째
블로그 운영

여러분의 방문을 환영해요! 🎉

다양한 개발 지식을 쉽고 재미있게 알려드리는 블로그가 될게요. 함께 성장해요! 😊

프로젝트/예약 사이트 프로젝트

[프로젝트] 리액트 쿼리: server데이터로 client 데이터를 어떻게 관리하지..

by 꽁이꽁설꽁돌 2024. 10. 5.
728x90
반응형

프로젝트를 하면서 리액트 쿼리를 쓰며 많은 고민한 부분과 해결과정을 정리해보려고 한다.

 

초기 랜더링

아래와 같은 예약 사이트가 있다고 하자

아래 화면은 리액트 쿼리의 서버 데이터를 받아 랜더링 된 화면이다.

 

받은 데이터는 일단 다음과 같다.

    "updated": datetime string, // "2024-09-07T09:37:12.881Z"
    "start": datetime string,
    "end": datetime string,
    "attendees": [
      {
        "email": "mooboongofficial@gmail.com",
        "displayName": "{\"id\":1,\"type\":\"personal\",\"name\":\"관리자\"}",
        "responseStatus": "accepted",
        "comment": "{\"id\":1,\"type\":\"personal\",\"name\":\"관리자\"}"
        // comment 가 참석자 json
      }
    ],
    "recurrence": boolean
  }
]

 

이런식으로 가공되지 않은 순수 데이터이다.그렇다면 이걸 어떻게 써야 내가 원하는 형식에 맞추어 쓸 수 있을까?

 

바로 query의 slect를 이용하면 된다!

참고로 반환된 데이터에는 영향을 주지만 쿼리 캐시에 저장되는 데이터에는 영향을 주지 않는다.

 

query select를 통한 데이터 변형

  const { data, isLoading } = useQuery({
    queryKey: ['reservation', formattedStartWeek],
    staleTime: 1000 * 60 * 3,
    queryFn: ({ signal }) => loadData({ formattedStartWeek, token, signal }),
    select: (data: any) => reservationMapConverting(data, startOfTheWeek),
  });

 

 

모달이 열릴 때 생긴 문제점

처음 랜더링까지는 좋았으나 그 이후가 문제였다...

아래와 같이 드래그 한 후에 확정되지 않은 클라이언트 데이터를 보여주어야 하는 것이었다

 

처음에는 쿼리를 state에 넣어서 해결할려 했으나 그렇게 되면 불필요한 행동을 한번 더 하게 되는 것이다

굳이 캐시에 저장되어 있는 데이터를 state저장소에 한번 더 넣는 비효율적인 일임과 동시에

데이터의 동기화를 추가로 신경 써야 하는 문제가 생긴다.

 

 

캐시 - 서버데이터

select - 클라이언트 데이터

 

위와 같이 볼 수 있다. 그렇다면 낙관적 업데이트를 이용해서 해결할 수 있다.

캐시에 임의의 데이터를 넣어 줌으로써 클라이언트 데이터를 변형시켜주는 것이다.

 

낙관적 업데이트를 통한 임시 클라이언트 데이터

export function mergeClientReservation(
  strIdx: number,
  endIdx: number,
  items: Pick<
    reservationInterface,
    | 'eventId'
    | 'summary'
    | 'updated'
    | 'start'
    | 'end'
    | 'attendees'
    | 'recurrence'
  >[],
) {
  const formattedStartWeek = useDateStore.getState().formattedStartWeek;
  //const summary = `${queryClient.getQueryData(['userinfo']).name} 개인연습`;
  //console.log('!!merge', summary);
  const strTime = items[strIdx].start;
  const endTime = items[endIdx].end;

  const targetData = {
    eventId: '', // 새로 추가하는 데이터의 경우 빈 값
    summary: '미예약', // 예약명 title
    updated: new Date().toISOString(), // ISO 8601 형식의 문자열로 변환
    start: strTime,
    end: addThirtyMinutes(endTime), // 30분 추가된 시간
    attendees: [{}], // 참석자 배열
    recurrence: false, // 반복 예약 여부
  };

  queryClient.setQueryData(
    ['reservation', formattedStartWeek],
    [
      ...(queryClient.getQueryData(['reservation', formattedStartWeek]) as any),
      targetData,
    ],
  );
}

여기서 주의할 것은 가공되지 않은 데이터를 추가해 주어야 한다.

 

모달이 닫혔을 때 생긴 문제점

그리고 이제 예약을 하지 않고 모달이 닫혔을 때는 아래와 같이 원래 상태로 돌아가야 한다.

모달이 닫히고 캐시가 사라진 모습

이건 어떻게 해야 할까? 의외로 간단할 수 있다 바로 queryClient.invalidateData를 써주어 캐시를 초기화 시켜주는 것이다

그러면 서버 데이터에서 원래 데이터를 받아오기에 사라지게 된다. 

 

 

 

하지만 문제가 있다 바로 서버를 거치기 때문에 랜더링이 늦어진다는 점이다 그렇다면 이걸 어떻게 해결할 수 있을까?

바로 setquery를 통해 초기 캐시 데이터로 만들어 버리는 것이다.

 

setquery를 통한 원상 복구

export function deleteClientReservation(restoreData: reservationLoadType[]) {
  const formattedStartWeek = useDateStore.getState().formattedStartWeek;

  queryClient.setQueryData(['reservation', formattedStartWeek], restoreData);
}

 

 

이렇게하면 서버를 거치지 않기기 때문에 빠른 랜더링을 할 수 있다.

 

 

 

++ 추후 수정 (중요)

낙관적 업데이트 외에는 setquerydata의 사용을 권장하지 않는다.

처음부터 클라이언트 데이터와 서버 데이터를 분리해서 구조를 짜는게 맞다.

뒤집어 엎고 다시 구현할 계획이다... 

 

아래 참고

https://be-senior-developer.tistory.com/241

 

[React] Query의 올바른 사용에 관해

목차  먼저 react query를 들어가기 전에 useState의 남용 문제를 살펴보아야 한다.useState의 남용서버로부터 데이터를 불러오고(카테고리 리스트) 사용자가 카테고리를 필터링할 수 있도록 하는 코

be-senior-developer.tistory.com

 

 

반응형