useMemo를 통한 무한 렌더링 방지

히니·2025년 10월 16일

workLogs

목록 보기
5/5

일단 발생되는 코드는 아래와 같다.

  1. query 코드
export function usePublishingGroupQuery() {
  const { businessId, propertyId } = useAuth();

  const {
    data: publishingGroup,
    isLoading: isPublishingGroupLoading,
    isFetching: isPublishingGroupFetching,
    error: publishingGroupError,
    refetch: refetchPublishingGroup,
  } = useQuery({
    enabled: !!businessId && !!propertyId,
    queryKey: generateDeployGroupsKey(),
    queryFn: () => fetchDeploysGroups({ business_id: businessId, property_id: propertyId }),
    staleTime: Infinity,
    select: (data: PublishingGroupResponseData) => data.result.deployGroups,
  });

  const deployedPublishingGroup = useMemo(() => publishingGroup?.filter((data) => data.project_id), [publishingGroup]);

  return {
    publishingGroup,
    deployedPublishingGroup,
    isPublishingGroupLoading,
    isPublishingGroupFetching,
    publishingGroupError,
    refetchPublishingGroup,
  };
}
  1. 호출하는 컴포넌트 코드
const TVPortal: React.FC<TVPortalProps> = ({ setPublishGroup, publishGroup }) => {
  const { t } = useTranslation();
  const { deployedPublishingGroup } = usePublishingGroupQuery();
  const { editor } = useEditorQuery();
  const { setPublishData, publishData } = useRoomManagerStore((state) => state.publish);

  useEffect(() => {
    if (editor && deployedPublishingGroup) {
      const _publishData = deployedPublishingGroup.map((data) => {
        const findOne = editor.result?.projects?.find((_data) => _data.project_id === data.project_id);
        const modified_date = findOne?.project?.modified_date;
        return {
          ...data,
          modified_date,
          project_id: data.project_id,
        };
      });
      if (_publishData?.length > 0) {
        setPublishData(_publishData as TVPublishDeployData[]);
        setPublishGroup(_publishData[0].deploy_id);
      }
    }
  }, [editor, deployedPublishingGroup, setPublishData, setPublishGroup]);


...... // 나머지는 생략 
}

🔄 왜 useMemo를 사용해야 했을까?

📌 문제 상황

usePublishingGroupQuery에서 deployedPublishingGroup을 다음과 같이 정의했을 때:

const deployedPublishingGroup = publishingGroup?.filter((data) => data.project_id);

이 코드가 매 렌더링마다 새로운 배열 객체를 생성하게 되면서, TVPortal 컴포넌트의 useEffect가 무한 호출되는 문제가 발생하였다.

⚙️ 실행 흐름 요약

  1. usePublishingGroupQuery 호출 → deployedPublishingGroup 생성 (새로운 배열 참조)
  2. TVPortal의 useEffect 실행 (의존성 배열에서 deployedPublishingGroup의 변경 감지)
  3. setPublishData 호출 → 상태 변경
  4. 상태 변경으로 컴포넌트 리렌더링
  5. 다시 1번으로 돌아가면서 무한 반복

🧠 왜 무한 호출이 발생했을까?

deployedPublishingGroup은 배열을 새로 생성하기 때문에, 값이 같더라도 참조가 매번 달라짐
useEffect는 참조가 바뀌면 실행되므로, 렌더링마다 실행됨
setPublishData는 상태를 변경하므로 또다시 렌더링 발생 → 무한 루프

const { setPublishData, publishData } = useRoomManagerStore((state) => state.publish);

만약에 useEffect에서 setPublishData호출하는 부분이 없어 스토어에 구독하는 데이터가 없다면 TVPortal은 리렌더링이 되지 않을것이다. 그러나 데이터가 변경됨을 감지함으로써 리렌더링이 발생하고 deployedPublishingGroup 데이터가 변경이 됨으로써 또 다시 무한호출로 이어진다.

스토어의 데이터가 변경되어야 하고, 변경후에는 리렌더링이 되어야 한다. 그렇다면 무한호출을 막을 수 있는 방법은 deployedPublishingGroup의 데이터의 값자체가 변경되지 않았음에도 불구하고 새로운 데이터 객체가 생성됨으로써 새로운 데이터라고 판단하는 부분을 고쳐야한다.

그렇기에 useMemo훅을 사용해서 실제 데이터값이 변경되었는지를 확인하고 의존성 배열로써 publishingGroup이 변경될떄만 새로운 객체를 생성해서 해결하였다.

✅ 해결 방법: useMemo 사용

const deployedPublishingGroup = useMemo(  () => publishingGroup?.filter((data) => data.project_id),  [publishingGroup]);

publishingGroup이 변경될 때만 deployedPublishingGroup을 새로 계산
값이 변경되지 않으면 같은 참조를 유지하므로 useEffect가 다시 실행되지 않음
결과적으로 불필요한 리렌더링과 무한 호출을 방지

profile
안녕하세요

0개의 댓글