[React] 내가 만든 Custom hooks

Empu·2024년 11월 24일

react

목록 보기
3/6
post-thumbnail

이번 전온팀 프로젝트를 진행하며 만들어서 사용한 custom hook 두가지를 소개해보고자 한다.

1. useBookmark

전온팀 프로젝트에는 즐겨찾기를 할 수 있는 기능들이 왕 많았다. (안내,콘텐츠,부스 등..)
그래서 즐겨찾기 진행하는 로직을 한군데에서 관리하고 하나의 훅을 여러곳에서 사용하기 위해 useBookmark 라는 훅을 만들었다.

export default function useBookmark({
  id,
  queryKey,
  bookmarkFn,
  bookmarkCancelFn,
  initialBookmarkState,
}: {
  id: number;
  queryKey: string;
  bookmarkFn: (id: number) => Promise<any>;
  bookmarkCancelFn: (id: number) => Promise<any>;
  initialBookmarkState: boolean | null;
}) {
  const queryClient = useQueryClient();
  const [like, setLike] = useState(initialBookmarkState || false);

  const mutation = useMutation({
    mutationFn: async (userAction: 'UNLIKE' | 'LIKE') => {
      if (userAction === 'LIKE') {
        await bookmarkFn(id); // 북마크 추가
      } else {
        await bookmarkCancelFn(id); // 북마크 취소
      }
    },
    onMutate: async (userAction) => {
      await queryClient.cancelQueries({
        queryKey: [queryKey, id],
      });

      const prevDetails = queryClient.getQueryData([queryKey, id]);

      queryClient.setQueryData([queryKey, id], (prev: any) => {
        if (!prev) return prev;
        return {
          ...prev,
          bookmark: userAction === 'LIKE',
        };
      });

      return { prevDetails };
    },
    onError: (_error, _, context) => {
      if (context?.prevDetails) {
        queryClient.setQueryData([queryKey, id], context.prevDetails);
      }
    },
    onSettled: () => {
      queryClient.invalidateQueries({
        queryKey: [queryKey, id],
      });
    },
  });


  useEffect(() => {
    if (initialBookmarkState !== null) {
      setLike(initialBookmarkState);
    }
  }, [initialBookmarkState]);

  const toggleBookmark = () => {
    const action = like ? 'UNLIKE' : 'LIKE';
    mutation.mutate(action);
    setLike((prev) => !prev); 
  };

  return {
    like,
    toggleBookmark,
  };
}
  • 매개변수로는 해당 즐겨찾기 게시물의 id, queryKey, 북마크 생성/취소 함수, 서버에서 가져오는 초기 bookmark(bool)값이 있다.
  • 반환 값으론 현재 북마크 상태인 like, 북마크 상태를 토글(추가/취소)하는 함수이다.

특징

1. react-query 활용

  • onMutate, onError, onSettled를 통해 서버 상태와 클라이언트 상태 간 동기화를 처리
  • invalidateQueries로 캐시 무효화를 사용

2. 낙관적 업데이트

  • onMutate와 setQueryData를 활용하여 사용자 경험을 향상
  • setLike를 즉시 업데이트하여 빠른 사용자 피드백 제공

3. 유지 보수

  • 훅이 재사용 가능하도록 설계되어, 다양한 컴포넌트에서 북마크 기능을 쉽게 적용 가능.

좀 더 고려할 점

  • 에러처리에 대한 명확성이 부족하다. onError를 통해 데이터를 복원하지만 사용자에게 에러 발생을 알리는 로직이 없기 때문에 사용자는 혼란을 느낄 수 있다. 이부분은 개선해보자.
  • useEffect로 초기 상태를 동기화 하고 있지만 mutate될 때, query를 사용하면 useEffect를 없애고 불필요한 렌더링을 줄일 수 있을 것 같다.
  • 이 기능은 상세 페이지에서만 사용되는 기능들인데, 리스트 목록들에서도 즐겨찾기가 가능하게 기능을 추가하고 목록들에서도 bookmark가 가능토록 코드를 수정해보고 싶다.

2. useDebounce

전온팀의 부스 검색 시 사용되는 debounce 훅이다. 사용자의 입력값 변화에 딜레이를 주어 불필요한 연산이나 API 호출을 줄이는 데 사용한다.

export default function useDebounce(value, delay) {
  const [debouncedValue, setDebouncedValue] = useState(value);

  useEffect(() => {
    const handler = setTimeout(() => {
      setDebouncedValue(value);
    }, delay);

    return () => {
      clearTimeout(handler);
    };
  }, [value, delay]);

  return debouncedValue;
}

특징

  1. 간결함
  • 코드가 단순하며 다양한 사항에서 재사용 될 수 있다. (전온팀에서는 검색에서만..ㅎ)
  1. 효율성
  • 불필요한 렌더링이나 API 호출을 최소화.

좀 더 고려할 점

  • react-query 라이브러리를 훅 안에서 사용해서 더 효과적으로 관리해주기
  • 딜레이 대신 애니메이션프레임 디바운스를 사용하면 더 부드러운 사용자 경험을 줄 수 있다고 한다.
profile
Life is a risk.

0개의 댓글