Recoil의 Selector를 이용한 페이지 내 일부 최신 상태로 변경

페이지 내 일부만 최신 상태로 변경하기

페이지 렌더링 시 처음부터 데이터를 사용하는 문제

진행 중인 프로젝트의 쉐어 상세 페이지 내에서, 현재 처음에 페이지 정보를 모두 받아와 보여주는 방식으로 구성이 되어 있습니다. 하지만, 이렇게 진행할 경우 사용자가 쉐어 신청 ∙ 취소를 할 때와 찜 하기 ∙ 취소를 할 때, 즉각적인 업데이트를 위해서는 페이지 정보를 모두 다시 받아와야 합니다. 이로 인해 버튼을 클릭할 때마다 페이지의 로딩이 이루어지고 다시 화면이 보이는 깜빡거림 문제가 발생하게 됩니다.

이를 해결하기 위한 방법으로 두 가지가 존재합니다.

  1. 서버와 호환하지 않고, 클라이언트에서 버튼 클릭 상황에 대한 값을 갖고 사진과 인원수 조절하기
  2. 버튼 클릭 후 서버와 통신이 이루어질 시 바로 해당 부분 업데이트하기

기존에는 1번의 방법으로 시도해 일부 성공하였으나, 백엔드와의 회의 후, 버튼을 클릭한 뒤에는 가능한 최신의 상태를 받는 것이 좋을 것이라는 결론에 도달했습니다. 이를 위해 기존에 클라이언트에서만 호환이 되던 상태를 없애고 API를 통해 받은 값을 사용해야했는데, 기존에 사용 중인 쉐어 상세 데이터를 다시 호출하는 것은 앞서 진행한 바와 같이, 화면의 깜빡임이 지속됩니다.

API 재호출 및 일부분 사용

문제를 해결하기 위해서 서버로부터 새로운 API를 요구해 받는 것은 무리가 있어, 기존 API를 통해 가져올 수 있었던 쉐어 상세 데이터 값 중 일부를 새롭게 설정하는 방법을 사용하기로 했습니다.

현재 버튼을 클릭하여 수행하며 바뀌는 값들을 쉐어 상세 데이터로부터 따로 빼내어 가져오는 것을 먼저 진행했습니다.

export type RecruitmentType =
  | Pick<
      ShareDetailType,
      | "recruitmentMemberThumbnailImageUrls"
      | "currentRecruitment"
      | "finalRecruitment"
      | "wishCount"
    >
  | undefined;

export const recruitmentTrigger = atom<number>({
  key: `recruitmentTrigger`,
  default: 0,
});

export const recruitmentState = selectorFamily<RecruitmentType, string>({
  key: `GET/recruitmentState/${getRandomKey()}`,
  get:
    (id: string) =>
    async ({ get }) => {
      get(shareDetailTrigger);
      // 쉐어 상세 데이터가 바뀔 경우 데이터를 다시 받아오기 위해 설정
      get(recruitmentTrigger);
      // 현제 상태값을 한 번 다시 바꿔주기 위해 사용
      const shareDetailData = await getShareDetailData({ id });
      // API를 통해 쉐어 상세 데이터 값을 받아옴
      if (!shareDetailData || typeof shareDetailData === "string") return;
      const {
        recruitmentMemberThumbnailImageUrls,
        currentRecruitment,
        finalRecruitment,
        wishCount,
      } = shareDetailData;
      // 데이터 중 필요한 것만 일부 빼내어 사용함
      return {
        recruitmentMemberThumbnailImageUrls,
        currentRecruitment,
        finalRecruitment,
        wishCount,
      };
    },
});

위와 같이 설정을 해주고, 이를 실제로 사용해 줄 위치에 받아와 사용하면 됩니다. 이 때, 이 데이터들이 실제로 적용되기 전에 보여져야 할 값들을 지정해주어야 하며 이를 위해서 초기값을 만든 뒤, API를 통해 값을 받은 뒤에는 해당 값을 보여주도록 설정했습니다.

// 초기값을 설정하여 상태값에 넣어줌
const defaultRecruitmentInfo: RecruitmentType = {
  recruitmentMemberThumbnailImageUrls: [writerThumbnailImageUrl],
  currentRecruitment: 1,
  finalRecruitment: 1,
  wishCount: 0,
};
const [curRecruitmentInfo, setCurRecruitmentInfo] = useState(defaultRecruitmentInfo);
const { state: recruitmentInfoState, contents: recruitmentInfo } = useRecoilValueLoadable(
  recruitmentState(`${id}`),
);
// 실제 사용될 값들을 설정
const { currentRecruitment, finalRecruitment, recruitmentMemberThumbnailImageUrls } =
  curRecruitmentInfo;

...

useEffect(() => {
  // API를 통해 호출된 값이 정상적으로 들어있을 경우 호출된 값을 보여줄 값으로서 설정
  if (recruitmentInfoState === 'hasValue' && recruitmentInfo) {
    setCurRecruitmentInfo(recruitmentInfo);
    return;
  }
}, [recruitmentInfoState]);

위와 같이 설정이 끝나면 마지막으로, 저 설정된 API들을 원하는 상황에도 새롭게 받아올 수 있도록 trigger들을 적용해주면 됩니다.

const changeParticipating = async () => {
  const isRequestSuccess = await request({ id });
  ...

  if (isRequestSuccess) {
    ...
    setRecruitmentTrigger((prev) => prev + 1);
    // 정상적으로 요청이 진행되었을 경우, trigger를 실행시켜 서버로부터 최신의 상태값을 받아오도록 함
  } else {
    message = '요청을 처리하지 못했습니다.';
  }

  ...
};

생각보다 어려웠던 이유

이 부분을 수행하며 처음에 가장 어려움을 느낀 부분은 ‘이미 페이지의 가장 처음 부분에서 쓰이는 값들이 있는 경우, 어떻게 일부분만 수정을 하도록 할 것인지’였습니다. 이번에 수정하며 얻은 답은 ‘새롭게 업데이트할 부분만 상태값을 다시 만들어준다’였지만, 가능하다면 이후에는 쉐어 상세 페이지의 모든 부분들이 깜빡임 없이 업데이트가 이루어질 수 있도록 수정할 필요가 있습니다.