[React] 커스텀 셀렉트 박스 무한 렌더링 삽질기 - flex 레이아웃 한 줄로 해결되다..

2026. 3. 17. 17:42·Development/React

JS 동적 계산과 CSS Flex의 상관관계



문제 상황

페이지에서 등록 폼을 만들었다. 목록을 선택하는 MultiChipSelectSearch 이라는 현재 프로젝트 내부의 커스텀 컴포넌트를 썼는데, chip을 3~4개 이상 선택하면 화면이 계속 떨렸다. 1~2개일 때는 괜찮았는데 개수가 늘어나니까 갑자기 깜빡거리기 시작했다.

 

chip이 많아져서 input 크기를 넘어가게되면, 더보기로 볼 수 있게 개수가 표시되는 구조인데, 그게 표시될 때 무한 렌더링이 발생했다.

 

이전에 개발하면서 테스트할 때는, 목록 데이터의 길이가 짧아서 위 사항을 테스트하지 못했었던 것이다.

사실 다른 오류가 있어서 수정하다가 확인하다보니 이것도 문제 였던 거지만..

 

API 응답조차 너무 느려서 더미 데이터로 교체하고 테스트 해봤다.

 

원인 분석

분석 1: 더미 데이터 만들기

더미 데이터를 이렇게 만들었다:

const DUMMY_CHANGE_FILES = selectedRepo.value ? [...] : [];
const changeFiles = { data: { data: DUMMY_CHANGE_FILES } };

문제는 이게 매 렌더마다 새 객체를 생성한다는 거였다. React Query를 쓰면 캐시가 참조를 안정적으로 유지해주는데, 인라인 리터럴 객체는 그게 안 된다.

changeFiles 새 객체 생성
  → changeFileMap useMemo 재계산
    → fileOptions useMemo 재계산 (항상 새 배열)
      → CheckboxSearchSelectList에 새 options 전달
        → 내부 useFilteredOptions의 useEffect([options]) 실행
          → 상태 변경 → 재렌더 → 무한 루프

이건 useMemo로 감싸서 해결했다. 근데 여전히 떨렸다.

 

 

분석 2: ChipsDropLayout의 ResizeObserver 루프

ChipsDropLayout은 칩이 컨테이너를 넘치면 "+N" 버튼을 보여준다. 이걸 계산하려고 useChipsLayout에서 ResizeObserver를 쓴다.

ResizeObserver: Resize(크기 변경) + Observer(관찰자)
resize 이벤트와 달리 브라우저는 물론 특정 dom의 크기 변화를 감지할 수 있다.

 

문제는 아래 로직이였다. 

소스

// useChipsLayout.ts
const observer = new ResizeObserver(() => {
  if (너비가 5px 이상 바뀌었으면) {
    setIsCalculating(true);  // 스켈레톤 표시
  }
  recalculate();
});

 

동작

isCalculating=true (스켈레톤 표시)
  → DOM 크기 변경
    → ResizeObserver 발동
      → setIsCalculating(true)
        → 스켈레톤 다시 표시 → ...무한 반복

1~2개 칩일 때는 오버플로우가 없어서 이 루프가 안 돌았고, 3개 이상부터는 "+N" 버튼이 생기면서 스켈레톤↔실제 칩이 계속 교체됐다.

이것도 useChipsLayout.ts를 수정해서 ResizeObserver에서는 스켈레톤 없이 재계산만 하도록 바꿨다. 근데 여전히 떨렸다.

 

 

원인

flex 레이아웃 미적용

결국 문제는 CSS였다.

// 수정 전
<div style={{ width: '70%' }}>
  <MultiChipSelectSearch ... />
</div>

// 수정 후
<div style={{ flex: 1, minWidth: 0 }}>
  <MultiChipSelectSearch ... />
</div>

 

 

왜 이게 문제였나

Flex 자식은 기본적으로 min-width: auto라서 내용보다 작게 줄어들지 않는다. chip이 많거나 텍스트가 길면 "내용 최소 너비"가 커지고, 그걸 기준으로 레이아웃이 잡힌다.

그래서:

  • 칩 영역 너비가 내용에 따라 계속 바뀌거나
  • 형제 요소(저장소 셀렉트 30%)와 공간을 나누는 과정에서 한 번에 정해지지 않음
  • ChipsDropLayout의 ResizeObserver가 "컨테이너 너비가 바뀌었다"고 인식
  • 오버플로우 재계산 → "+N" 버튼 표시 여부 변경 → DOM 변경 → 또 너비 바뀜 → ...

 

해결

  • flex: 1: 남는 공간을 채우고, 필요하면 줄어들 수 있게 함
  • minWidth: 0: min-width: auto를 덮어서 내용 최소 너비보다 작게도 줄어들 수 있게 함

그래서 칩 컨테이너의 너비가 "행에서 남은 공간"으로 고정되고, 한 번 계산된 뒤에는 레이아웃이 안정되어 ResizeObserver가 반복해서 호출되지 않았다.

수정 후 CLS

 

교훈

  1. Flex 레이아웃에서 minWidth: 0은 필수
    특히 동적으로 크기가 바뀌는 컴포넌트(칩, 태그 등)를 감쌀 때는 flex: 1, minWidth: 0을 항상 같이 써야 한다.
  2. ResizeObserver 루프는 CSS 문제일 수 있다
    컴포넌트 내부 로직만 보지 말고, 부모의 레이아웃 제약도 확인해야 한다.

결국 한 줄 CSS로 해결됐는데, 여기까지 오는 데 몇 시간이 걸렸다. 다음엔 동적 너비 컴포넌트를 쓸 때는 스타일도 확인해보자.

AI로는 안잡힘 ㅜㅜ..

 

 

참고

https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserver

 

ResizeObserver - Web APIs | MDN

 

developer.mozilla.org

https://developer.mozilla.org/ko/docs/Web/CSS/Reference/Properties/flex

 

flex - CSS: Cascading Style Sheets | MDN

 

developer.mozilla.org

 

'Development > React' 카테고리의 다른 글

[React] React Query: "enabled: false"인데 왜 자꾸 fetch가 튀어 나갈까? (feat. PR #3223)  (0) 2026.02.05
[React-Query] queryFn에서 언랩(unwrapping) 하는 것과 select 옵션으로 언랩하는 것의 차이  (0) 2025.09.23
[ReactFlow] 이제 reactflow 대신 @xyflow/react  (1) 2025.08.18
[React] ReactQuery 순환 참조  (1) 2025.06.09
[React] React에서 key 중복으로 발생한 렌더링 이슈 (Reconciliation 사례)  (1) 2025.05.27
'Development/React' 카테고리의 다른 글
  • [React] React Query: "enabled: false"인데 왜 자꾸 fetch가 튀어 나갈까? (feat. PR #3223)
  • [React-Query] queryFn에서 언랩(unwrapping) 하는 것과 select 옵션으로 언랩하는 것의 차이
  • [ReactFlow] 이제 reactflow 대신 @xyflow/react
  • [React] ReactQuery 순환 참조
곽진돔
곽진돔
Developer
  • 곽진돔
    echo "곽박한 세상";
    곽진돔
  • 전체
    오늘
    어제
    • 분류 전체보기 (232)
      • Development (95)
        • Linux (13)
        • k8s (3)
        • Docker (5)
        • AWS (1)
        • PHP (35)
        • Python (21)
        • Java (5)
        • SpringBoot (4)
        • JavaScript (2)
        • React (12)
        • MySql (19)
        • MongoDB (1)
      • Daily (8)
      • Study (7)
        • TIL (2)
        • license (3)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
    • 글쓰기
    • 설정
  • 링크

    • github
  • 공지사항

  • 인기 글

  • 태그

    docker
    IP
    Selenium
    springboot
    정규표현식
    HTML
    react
    JavaScript
    nodejs
    Mac
    MySQL
    크롤링
    AI
    db
    Python
    스프링부트
    리눅스
    SQL
    CentOS7
    CentOS
    Java
    php
    GitHub
    chromedriver
    Linux
    Shell
    UTF8
    Git
    인코딩
    ssh
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.6
곽진돔
[React] 커스텀 셀렉트 박스 무한 렌더링 삽질기 - flex 레이아웃 한 줄로 해결되다..
상단으로

티스토리툴바