📄Today I Learned

연락처 즐겨찾기 기능 트러블 슈팅

영진학생 2025. 6. 10. 21:02

최종 프로젝트 기간 중간에 유저 테스트를 진행 중 피드백을 받았다.
그래서 즐겨찾기 기능 구현이 필요하다고 생각이 들었음

  • 피드백 내용: "내가 찾고싶은 사용자를 리스트에서 찾는 데 시간이 오래 걸린다."

30명의 사용자 테스트 후 DB 분석 결과, 전체 연락처 중 특정 연락처만 반복해서 조회하는 패턴 발견했다

 

💡기능 구현 방법

1. 데이터베이스 구조 활용

기존 contacts 테이블의 is_pinned 컬럼(Boolean) 활용하여 즐겨찾기 상태를 관리하게 함

 

2. 백엔드 API 구현

contacts 테이블에 boolean 값을 변경할 업데이트 함수를 생성

 
// 핀 업데이트 함수
export const mutateUpdateContactPin = async (contactId: string, isPinned: boolean) => {
  try {
    const { data, error } = await supabase
      .from('contacts')
      .update({ is_pinned: isPinned })
      .eq('contacts_id', contactId)
      .select();  
    
    if (error) {
      throw error;
    }
    
    return data;
  } catch (error) {
    console.error('연락처 핀 업데이트 실패:', error);
    throw error;
  }
};

 

3. Tanstack Query를 활용한 상태 관리

// Pin 업데이트 뮤테이션
const pinMutation = useMutation({
  mutationFn: ({ contactId, isPinned }: { contactId: string; isPinned: boolean }) => 
    mutateUpdateContactPin(contactId, isPinned),
  onSuccess: () => {
    // 성공 시 연락처 목록 갱신 (새로고침 없이 실시간 업데이트)
    queryClient.invalidateQueries({ queryKey: ['contacts', TEST_USER_ID] });
  }
});

3-1. 연락처 분리 및 정렬 로직

// 핀된 연락처와 일반 연락처 분리
const { pinnedContacts, regularContacts } = useMemo(() => {
  const pinned = contacts.filter(contact => contact.is_pinned);
  const regular = contacts.filter(contact => !contact.is_pinned);
  return { pinnedContacts: pinned, regularContacts: regular };
}, [contacts]);

3-2. Pin 버튼 UI 구현

// 호버 효과를 위한 상태 관리
const [isPinHovering, setIsPinHovering] = useState(false);

// Pin 토글 핸들러
const handlePinClick = (e: React.MouseEvent) => {
  e.preventDefault();
  e.stopPropagation();
  onTogglePin(contact.contacts_id, !contact.is_pinned);
};

3-3. 조건부 스타일링 적용

<button 
  onClick={handlePinClick}
  onMouseEnter={() => setIsPinHovering(true)}
  onMouseLeave={() => setIsPinHovering(false)}
  className={`mr-5 transition-opacity duration-200 ${
    contact.is_pinned 
      ? 'text-gray-800' 
      : isPinHovering 
        ? 'opacity-70' 
        : 'opacity-20'
  }`}
  aria-label={contact.is_pinned ? "즐겨찾기 해제" : "즐겨찾기 추가"}
>
  <MapPinSimple size={24} weight={contact.is_pinned ? "fill" : "regular"} />
</button>

UI/UX 개선

연락처 목록 구조

  • 고정됨 section: 즐겨찾기된 연락처들을 상단에 별도 섹션으로 표시
  • 리스트 section: 일반 연락처들을 하단에 표시

사용자 경험 개선

  • 시각적 피드백: Pin 상태에 따른 아이콘 변화
  • 호버 효과: 비활성 상태에서도 호버 시 시각적인 효과를 줌

 

그리고 실시간으로 리스트를 업데이트 해주기 위해 Tanstack Query의 쿼리 무효화를 사용함

  • Tanstack Query의 invalidateQueries를 활용하여 새로고침 없이 즉시 UI 반영
  • 사용자가 Pin 버튼 클릭 시 즉시 화면 업데이트 경험 제공할수 있게 만듬

효율적인 상태 분리를 위해

  • useMemo를 활용하여 연락처 데이터 분리 로직 최적화
  • 불필요한 재렌더링 방지

각 리스트 안에 즐겨찾기 버튼이 있다보니 이벤트 버블링을 제어할 필요도 있었다.

  • e.preventDefault()와 e.stopPropagation()으로 Pin 버튼과 연락처 선택 이벤트 분리

 

🎯 배운 점

이번에 추가한 기능을 통해 사용자 피드백이 중요하다는 걸 느꼈다. 내가 예상하지 않은 부분도 유저 테스트를 통해 발견할수 있었고 또 DB 사용 패턴 분석을 통해서 구체적인 해결 방향도 발견할 수 있었다.