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

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

꽁이꽁설꽁돌 2024. 10. 5. 02:47
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

 

 

반응형