Skip to content

리액트 쿼리 사용 컨벤션

Kim Dong Hyun edited this page Nov 4, 2023 · 1 revision

모든 리액트 쿼리와 관련된 기능은 따로 파일을 생성하여 관리합니다.

특정 컴포넌트에서 직접적으로 useQuery, useMutation 호출 X.

파일은 src/hooks/api 폴더에 생성합니다.

api 폴더 하위에 따로 데이터 모델에 따라 폴더를 나눌지 의논 (예 items, auth, history 등)

단건 조회일 경우 use[모델명]Query , 다건 조회일 경우 use[모델명]sQuery로 작명합니다. (예 useItemQuery, useItemsQuery)

use[모델명]sQuery로 할지, use[모델명]ListQuery로 할지 의논

Mutation의 경우, post는 use[모델명]CreateMutation put은 Update, delete는 Delete로 작명합니다. (예 useItemCreateMutation)

쿼리 작성 예시

export const useItemsQuery = ({
  category,
  priceRange,
  name,
  status,
  size,
}: UseItemsQuery) => {
  return useInfiniteQuery({
    queryKey: ['items', category, priceRange, name, status, size],
    queryFn: async ({ pageParam }) =>
      await getItems({
        category,
        priceRange,
        name,
        status,
        size,
        cursorId: pageParam,
      }),
    initialPageParam: 0,
    getNextPageParam: (lastPage, allPages, lastPageParam) => {
      if (lastPage.length === 0) {
        return undefined
      }
      return lastPageParam + 1
    },
  })
}

적용 예시

const ItemList = () => {
  const PAGE_SIZE = 5
  const methods = useForm<ItemFilterInputs>()
  const { getValues } = methods

  const lastElementRef = useRef<HTMLDivElement | null>(null)
  const entry = useIntersectionObserver(lastElementRef, { threshold: 1.0 })

  const { data, fetchNextPage, isFetchingNextPage } = useItemsQuery({
    category: getValues('category'),
    priceRange: getValues('priceRange'),
    name: getValues('name'),
    status: getValues('status'),
    size: PAGE_SIZE,
  })

  useEffect(() => {
    if (isFetchingNextPage) {
      return
    }

    if (entry?.isIntersecting) {
      fetchNextPage()
    }
  }, [entry?.isIntersecting, fetchNextPage, isFetchingNextPage])

  return (
    <div>
      <div className="h-9 flex justify-between items-center mb-6">
        <FormProvider {...methods}>
          <SearchInput />
        </FormProvider>
        <div className="h-6 flex gap-2">
          <Image src={Assets.filterIcon} alt="필터 아이콘" />{' '}
          <div className="flex">필터</div>
        </div>
      </div>
      <div className="">
        {data?.pages.map((group, i) => (
          <React.Fragment key={i}>
            {group.map((item: Item) => (
              <TradeStateCard key={item._id} item={item} className="mb-6" />
            ))}
          </React.Fragment>
        ))}
        {isFetchingNextPage && '데이터 불러오는 중'}
      </div>
      <div ref={lastElementRef} />
    </div>
  )
}
export default ItemList