Frontend/Today I Learned

[React] TanStack Query로 custom hook 만들기

joycie416 2024. 10. 31. 22:56

한 페이지에 여러 데이터를 불러오게 되면 `useQuery'를 불러오는 데이터 개수만큼 사용해야 한다. 하지만 여러 데이터를 불러올수록 비슷한 코드가 여러 번 작성되고, 가독성도 떨어지는 단점이 생긴다. 이를 방지하기 위해 tanStack Query를 이용해 custom hook을 만들 수 있다.

 

사용자와 사용자가 등록한 아이 정보가 있다고 가정하겠다.

 

1. Custom Hook 만들기

Custom hook을 만드는 과정은 굉장히 쉽다. 우리가 컴포넌트 내부에서 쓰던 것처럼 사용하면 된다. 예를 들어 supabase 서버에서 로그인 정보를 가져오는 코드를 살펴보자.

// Schedule.tsx

"use client";

import { useChildrenQuery, useUserQuery } from "@/api/userApi";
import browserClient from "@/utils/supabase/client";

const Schedule = () => {
  const {
    data: user,
    isLoading: isUserLoading,
    isError: isUserError
  } = useUserQuery(browserClient);


  if (isUserLoading ) {
    return <div>Loading...</div>;
  }

  if (isUserError ) {
    return <div>Error.</div>;
  }

  return (
    ...
  );
};

export default Schedule;
// userApi.ts

import { SupabaseDatabase } from "@/types/supabaseDataType";
import { useQuery } from "@tanstack/react-query";

// supabase에서 로그인 정보 가져오기
export const getUser = async (supabaseClient: SupabaseDatabase) => {
  const {
    data: { user },
    error
  } = await supabaseClient.auth.getUser();

  if (error || !user) {
    console.error(error);
    return;
  }

  return user;
};

// 로그인 정보 가져오기
export const useUserQuery = (supabaseClient: SupabaseDatabase) => {
  return useQuery({
    queryKey: ["user", "client"],
    queryFn: () => getUser(supabaseClient)
  });
};

 

이처럼 기존에 컴포넌트에서 가져오던대로 return에 적어주면 된다.

 

 

2. 여러 커스텀 hook 동시에 사용하기

그런데 만약 user.id가 필요한 데이터를 가져오게 하려면 어떻게 해야할까? 이때 `useQuery`의 option이 중요하다.

 

로그인 정보를 불러오는 hook 이전에 사용자의 아이 정보를 불러오면 실행되지 않을 것이다. 따라서 로그인 정보를 불러오는 hook을 먼저 실행한 후에 사용자 관련 데이터를 불러와야한다. 하지만 user.id가 로그인 정보가 다 불어와지기 전에 custom hook에 argument로 입력되므로 undefined 값이 argument로 입력될 것이다.

 

이때 `useQuery`의 `enabled` 옵션을 사용하면 된다. 특정 조건일 때 `enabled`가 true/false가 되도록 설정하면 `queryFn`이 실행될지 안 될지를 결정할 수 있다. 여기서는 'user.id가 존재할 때'가 `enabled`의 조건이 될 것이다.

// Schedule.tsx

"use client";

import { useChildrenQuery, useUserQuery } from "@/api/userApi";
import browserClient from "@/utils/supabase/client";

const Schedule = () => {
  const {
    data: user,
    isLoading: isUserLoading,
    isError: isUserError
  } = useUserQuery(browserClient);
  
  const {
    data: childrenData,
    isLoading: isChildrenLoading,
    isError: isChildrenError
  } = useChildrenQuery(browserClient, user?.id);

  if (isUserLoading || isChildrenLoading) {
    return <div>Loading...</div>;
  }

  if (isUserError || isChildrenError) {
    return <div>Error.</div>;
  }

  return (
    ...
  );
};

export default Schedule;
// userApi.ts

...

// 로그인 정보 가져오기
export const useUserQuery = (supabaseClient: SupabaseDatabase) => {
  return useQuery({
    queryKey: ["user", "client"],
    queryFn: () => getUser(supabaseClient)
  });
};

...

// 사용자의 아이들 정보 가져오기
export const useChildrenQuery = (supabaseClient: SupabaseDatabase, userId?: string) => {
  return useQuery({
      queryKey: ["child_info", userId],
      queryFn: () => getChildren(supabaseClient, userId),
      enabled: !!userId
    });
};

 


사실 다른 문제점이 있었고, 깨달음을 얻은 부분이 있었는데 까먹어 버렸다 ㅜㅜ TanStack Query Docs에서 제공하는 ai랑 영어로 열심히 대화하면서 해결한 부분인데, 해당 창을 실수로 꺼버려서 대화 내역이 모두 사라져버렸다..