# Threshold Slider Filter ## 언제 쓰는가 - 사용자가 "얼마 이상만 보기" / "반경 N km 이내만" 같은 수치 임계값을 즉시 바꾸면서 결과를 확인하고 싶을 때 - 슬라이더 드래그 중에 매 프레임 서버 요청을 보내면 안 될 때 (디바운스 필요) - UI 반응성(값 표시)과 서버 부하(fetch) 를 분리해서 최적화해야 할 때 ## 핵심 구조 - **이중 상태 분리**: - UI 상태(슬라이더 position): `onChange`에 즉시 업데이트 → 숫자 표시도 즉시 반영 - 서버 파라미터: `useEffect` + `setTimeout(300ms)`로 디바운스 후 반영 - **반응형 레인지**: `` 로 네이티브 사용, 값 라벨은 `tabular-nums` 폰트로 폭 고정 - **단위 표기**: min/max가 의미를 가지면 label에 단위 표시 (예: "회", "km") - **명시적 최소/최대**: 코드 상수로 한계값을 정의해 UI/서버 양쪽에서 동일 범위 사용 - **컨테이너 책임**: 슬라이더는 prop으로 값/onChange만 받는 pure 컴포넌트, 디바운스는 컨테이너의 useEffect에 둠 ## 코드 예시 ```jsx // FilterBar.jsx (pure)
Min count onMinCountChange(parseInt(e.target.value, 10))} className="w-24 accent-primary" /> {minCount}
// Container.jsx (debounced refetch) useEffect(() => { const h = setTimeout(() => { fetch(`/api/data?minCount=${minCount}`).then(...) }, 300) return () => clearTimeout(h) }, [minCount]) ``` ## 주의사항 - 디바운스 시간은 보통 200~400ms. 너무 짧으면 드래그 중 과도한 요청, 너무 길면 "응답이 느리다"는 체감 - 슬라이더 min이 0이 되면 "의미 없음/전체"로 해석되기 쉬운데 서버가 이를 어떻게 처리할지 일관 정의할 것 (예: 0 = "전체"로 매핑) - 디바운스 유실 방지: AbortController로 이전 요청 취소 병행 권장 - 모바일 터치 이벤트에서 step 크기가 너무 작으면 조작이 어려움. `step` 명시하고 단계 수를 5~10 이내로 제한 - 접근성: 숫자 라벨 옆에 `aria-label`로 현재 값 제공. 키보드 ←→ 조작을 가정하고 min/max 간격을 너무 넓게 잡지 말 것 ## 재사용 방법 1. 슬라이더 JSX 블록을 그대로 복사, min/max/label만 교체 2. 컨테이너에 `useEffect` + setTimeout 디바운스 블록 추가 3. 서버 엔드포인트에서 `parseInt + clamp` 로 범위 외 값 방어 4. 참고 구현: `Obsidian_manage/src/components/growth/patterns/PatternFilterBar.jsx`의 `Min count` 슬라이더, `PatternsView.jsx`의 useEffect debounce