TanStack Query์™€ ํ•จ๊ป˜ ๊ตฌํ˜„ํ•œ cursor ๋ฐฉ์‹์˜ ๋ฌดํ•œ ์Šคํฌ๋กค

2024. 12. 10. 20:02ยทWhat I Learn

์ด๋ฒˆ ํ”„๋กœ์ ํŠธ์—์„œ๋Š” ๋ชจ๋ฐ”์ผ ์‚ฌ์šฉ์ž๋ฅผ ํƒ€๊ฒŸ์œผ๋กœ ์ง„ํ–‰ํ–ˆ๊ธฐ์— ๋ชจ๋ฐ”์ผ์—์„œ ๋” ์‚ฌ์šฉ์„ฑ์ด ์ข‹์€ ๋ฌดํ•œ ์Šคํฌ๋กค์„ ๊ตฌํ˜„ํ•˜๊ฒŒ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ์ด์ „์—๋Š” offset ๋ฐฉ์‹๋งŒ์„ ์‚ฌ์šฉํ•ด์„œ ๊ตฌํ˜„ํ•ด๋ณด์•˜๋Š”๋ฐ ์ด๋ฒˆ์—๋Š” cursor ๋ฐฉ์‹์„ ์„ ํƒํ•ด์„œ ์ž‘์—…ํ–ˆ์Šต๋‹ˆ๋‹ค.

๋‚ฏ์„  cursor ๋ฐฉ์‹์„ ์„ ํƒํ•œ ์ด์œ ๋Š” offset ๋ฐฉ์‹์œผ๋กœ ๊ตฌํ˜„ํ•˜๋ฉด ๊ฑด๋„ˆ๋›ธ ํŽ˜์ด์ง€๋ฅผ ์ง€์ •ํ•ด์„œ ๋ฐ์ดํ„ฐ๋ฅผ ์š”์ฒญํ•˜๊ณ  ์‘๋‹ต๋ฐ›๊ธฐ ๋•Œ๋ฌธ์— ํ•ด๋‹น ํŽ˜์ด์ง€์—์„œ ์‹ค์‹œ๊ฐ„์œผ๋กœ ๋ฐ์ดํ„ฐ ๋ฆฌ์ŠคํŠธ๋“ค์˜ ๋ณ€๊ฒฝ์‚ฌํ•ญ์ด ์žˆ์–ด๋„ ์Šคํฌ๋กค์‹œ ๋ณ€๊ฒฝ๋˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์— ๋ฐ์ดํ„ฐ์˜ ์ผ๊ด€์„ฑ์„ ๋ณด์žฅํ•  ์ˆ˜ ์—†๋‹ค๊ณ  ์ƒ๊ฐํ–ˆ๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค.

๋ฐฑ์—”๋“œ์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ๋ณด๋‚ผ ๋•Œ ๋” ๋ถˆ๋Ÿฌ์˜ฌ ๋ฐ์ดํ„ฐ๊ฐ€ ์žˆ์„ ๋•Œ๊นŒ์ง€ nextCursor ๊ฐ’์„ ๋ณด๋‚ด์ฃผ๊ธฐ๋กœ ๋…ผ์˜ํ–ˆ๊ณ , API์—์„œ๋Š” type, cursor, limit, order ๊ฐ’์ด ์ฟผ๋ฆฌ๋กœ ์ฃผ์–ด์กŒ๊ณ  ๊ทธ ์ค‘ type๋งŒ ํ•„์ˆ˜ ๊ฐ’์œผ๋กœ, ๋‚˜๋จธ์ง€๋Š” ์˜ต์…”๋„์ด์—ˆ์Šต๋‹ˆ๋‹ค.

 

export interface useGetMessageListProps {
	userId: string
	type: MessageType
	cursor?: Date | null
	limit?: number
	order?: string
}

 

 

์ด ์กฐ๊ฑด์— ๋งž์ถ”์–ด react-query์˜ useQuery๋ฅผ ์‚ฌ์šฉํ•ด ๋ฐ์ดํ„ฐ๋ฅผ ๋ถˆ๋Ÿฌ์˜ค๊ณ , ์ฒ˜์Œ ์š”์ฒญํ•ด์„œ ๋ฐ›์€ ๋ฐ์ดํ„ฐ์— nextCursor๊ฐ’์ด ์žˆ์„ ๋•Œ๊นŒ์ง€ useInView๋ฅผ ๊ฐ€์ง€๊ณ  ๋‹ค์Œ ๋ฐ์ดํ„ฐ๋“ค์„ ๋ถˆ๋Ÿฌ์˜ค๋„๋ก ํ–ˆ์Šต๋‹ˆ๋‹ค.

 

๋จผ์ € useQuery๋ฅผ ์‚ฌ์šฉํ•ด์„œ api๋ฅผ ํ˜ธ์ถœํ•  ๋•Œ, ํ•„์ˆ˜๊ฐ’์œผ๋กœ ๋ณด๋‚ด์•ผํ•˜๋Š” userId์™€ type๋Š” baseUrl๋กœ ๋‘๊ณ  ๋‚˜๋จธ์ง€ cursor, limit, order๋Š” params๋ผ๋Š” ๋ณ€์ˆ˜๋ช…์œผ๋กœ ํ•ฉ์ณ์„œ, ์ตœ์ข… url์„ ์กฐํ•ฉํ–ˆ์Šต๋‹ˆ๋‹ค.

 

ํ† ํฐ์ด ์žˆ๋Š”๋ฐ๋„ ํ”„๋ก ํŠธ์—์„œ userId๋ฅผ ๋ณด๋‚ด๋Š” ์ด์œ ๋Š” ๊ธฐ์กด 1์ฐจ ๊ฐœ๋ฐœ๋ฌผ์ด ์žˆ๋Š” ์ƒํƒœ์—์„œ ์ถ”๊ฐ€ ๊ฐœ๋ฐœ์„ ํ•˜๋Š” ํ™˜๊ฒฝ์ด์—ˆ๊ธฐ ๋•Œ๋ฌธ์— ๋ฐฑ์—”๋“œ์™€ API ํ‘œํ˜„๋ฐฉ์‹์— ๋Œ€ํ•œ ๋…ผ์˜๋ฅผ ํ• ๋•Œ RESTfulํ•จ์„ ์œ ์ง€ํ•˜๊ธฐ ์œ„ํ•˜์—ฌ ํ˜„์žฌ ๋ฐฉ์‹์œผ๋กœ ํ•ฉ์˜ํ–ˆ์Šต๋‹ˆ๋‹ค.

 

export const useGetMessageList = ({
  userId,
  type,
  cursor,
  limit,
  order,
}: useGetMessageListProps) => {
  return useQuery<MessageResponse, Error>({
    queryKey: ['getMessageList', userId, type, cursor, limit, order],
    queryFn: async () => {
      const baseUrl = `/v2/users/${userId}/messages?type=${type}`
      const params = new URLSearchParams({
      ...(cursor && { cursor: cursor.toString() }),
      ...(limit && { limit: limit.toString() }),
      ...(order && { order }),
      })

      const url = params.toString()
        ? `${baseUrl}&${params.toString()}`
        : baseUrl

      try {
        const { data } = await baseQuery.get(url, {
          headers: {
            Authorization: `Bearer ${localStorage.getItem('keep_in_touch_token')}`,
          },
        })
        if (!data) {
          throw new Error('No data received')
        }
        return data
      } catch (error) {
        console.error('Error fetching messages:', error)
        throw Error
      }
    },
  })
}

 

 

๋ฆฌ์ŠคํŠธ API๋ฅผ ํ˜ธ์ถœํ•  ์ปดํฌ๋„ŒํŠธ์—์„œ ์ƒํƒœ๊ด€๋ฆฌํ•  ๊ฐ’์€ useState๋กœ ๊ด€๋ฆฌํ–ˆ์Šต๋‹ˆ๋‹ค.

์ƒํƒœ๊ด€๋ฆฌํ•  ๊ฐ’์œผ๋กœ๋Š” ํŽ˜์ด์ง• ๊ด€๋ฆฌ๋ฅผ ์œ„ํ•œ cursor, API๋กœ๋ถ€ํ„ฐ ๋ฐ›์€ data๋ฅผ ์ €์žฅํ•˜๋Š” ์—ญํ• ์„ ํ•˜๋Š” messages๋ฅผ ์ž‘์„ฑํ–ˆ๊ณ  ๋‹ค์Œ๊ณผ ๊ฐ™์ด useGetMessageList ํ›…์„ ํ˜ธ์ถœํ•ด์„œ data fetching ๋กœ์ง์„ ์ž‘์„ฑํ–ˆ์Šต๋‹ˆ๋‹ค.

 

export default function MessagesBlock({
  type,
  userId,
}: {
  type: MessageType
  userId: string
}) {
  const [cursor, setCursor] = useState<Date | null>(null)
  const [messages, setMessages] = useState<MessageResponse>()
  const [hasMore, setHasMore] = useState(true)

  const { data, isLoading } = useGetMessageList({
    userId,
    type,
    cursor,
    limit: 10,
    order: 'desc',
  })

...

 

 

useGetMessageList ํ›…์„ ํ˜ธ์ถœํ•ด ๋ฐ›์€ data ํƒ€์ž…์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

export interface MessageResponse {
	receiveMessageCount?: number
	sentMessageCount?: number
	unreadMessageCount?: number
	nextCursor: Date | null
	messageList: Message[]
}

 

์‚ฌ์šฉ์ž์—๊ฒŒ ๋ณด์—ฌ์ค„ ์ชฝ์ง€ ๋ฆฌ์ŠคํŠธ ๋ฐฐ์—ด์€ messageList ๋‚ด์— ๋ฐฐ์—ด๋กœ ์žˆ๊ณ , ๋ฐ›์•„์˜จ nextCursor ๊ฐ’์„ cursor๊ฐ’์œผ๋กœ ํ˜ธ์ถœํ•ด์•ผํ–ˆ๊ธฐ์— setCursor๋กœ cursor๊ฐ’์„ ๊ด€๋ฆฌํ–ˆ์Šต๋‹ˆ๋‹ค.

 

๋จผ์ € ๋งŒ์กฑํ•ด์•ผํ•˜๋Š”์š”๊ฑด๋“ค์„ ์ž‘์„ฑํ–ˆ์Šต๋‹ˆ๋‹ค.

  • ์ดˆ๊ธฐ ๋ฐ์ดํ„ฐ ํ˜ธ์ถœ์‹œ ๋ฐ›์€ nextCursor ๊ฐ€ null ์ด ์•„๋‹ˆ๋ฉด ๋‹ค์Œ ๋ฐ์ดํ„ฐ ํ˜ธ์ถœ์‹œ ๊ทธ ๊ฐ’์„ cursor์— ๋‹ด์•„ ๋ณด๋‚ธ๋‹ค
  • ๋ฐ›์€ ๋ฐ์ดํ„ฐ๋Š” ๊ฐ€์ง€๊ณ  ์žˆ๋‹ค๊ฐ€ ํ˜ธ์ถœ์‹œ๋งˆ๋‹ค ๋ณ€๊ฒฝ๋œ ์ƒํƒœ ์—…๋ฐ์ดํŠธ
  • ๋ทฐํฌํŠธ๋ฅผ ๋ชจ๋‹ˆํ„ฐ๋งํ•˜๋‹ค๊ฐ€ ๋งˆ์ง€๋ง‰ ์•„์ดํ…œ์ด ๋ทฐ์— ๋“ค์–ด์™”์„ ๋•Œ ๋‹ค์Œ ๋ฐ์ดํ„ฐ๋ฅผ ํ˜ธ์ถœํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•œ๋‹ค. 

์œ„ ์š”๊ตฌ์‚ฌํ•ญ์„ ์ •๋ฆฌํ•ด์„œ, cursor๊ฐ€ ์กด์žฌํ•œ๋‹ค๋ฉด ํ˜„์žฌ ๋ฉ”์‹œ์ง€ ๋ฆฌ์ŠคํŠธ ๋ฐ์ดํ„ฐ์™€ ์ƒˆ๋กœ์šด ๋ฉ”์„ธ์ง€ ๋ชฉ๋ก ๋ฐ์ดํ„ฐ๋ฅผ ๊ฒฐํ•ฉํ•˜๋Š” updateMessages ํ•จ์ˆ˜๋ฅผ ์ž‘์„ฑํ–ˆ์Šต๋‹ˆ๋‹ค.

 

 useEffect(() => {
    if (data?.messageList) {
      const newMessageList = data.messageList
      setMessages((prev) => {
        const updatedMessageList = cursor
          ? [
              ...(prev?.messageList || []),
              ...newMessageList.filter(
                (newMsg) =>
                  !(prev?.messageList || []).some(
                    (prevMsg) => prevMsg.messageId === newMsg.messageId
                  )
              ),
            ]
          : newMessageList

        return {
          receivedMessageCount:
            data.receivedMessageCount || prev?.receivedMessageCount,
          sentMessageCount: data.sentMessageCount || prev?.sentMessageCount,
          unreadMessageCount:
            data.unreadMessageCount || prev?.unreadMessageCount,
          nextCursor: data.nextCursor ?? prev?.nextCursor ?? null,
          messageList: updatedMessageList,
        }
      })

      setHasMore(!!data.nextCursor)
    }
  }, [data, cursor])

 

๋ฐ˜ํ™˜๊ฐ’์€ ์ •๋ฆฌํ•˜๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

  • useEffect๋Š” data?.messageList๊ฐ€ ์กด์žฌํ•˜๋Š” ๊ฒฝ์šฐ์—๋งŒ ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค.
  • ์ˆ˜์‹ ๋œ, ๋ฐœ์‹ ๋œ, ์ฝ์ง€ ์•Š์€ ๋ฉ”์‹œ์ง€์˜ ๊ฐœ์ˆ˜ ์—…๋ฐ์ดํŠธ๋ฅผ ํ•˜๋Š”๋ฐ ๋งŒ์•ฝ data๊ฐ€ undefined์ผ ๊ฒฝ์šฐ ์ด์ „ ๊ฐ’์„ ์œ ์ง€ํ•ฉ๋‹ˆ๋‹ค.
  • newMessageList๋ฅผ data.messageList๋กœ ์ •์˜ํ•˜๊ณ , ์ƒํƒœ ์—…๋ฐ์ดํŠธ ๋กœ์ง์„ ํ†ตํ•ด ์ƒˆ๋กœ์šด ๋ฐ์ดํ„ฐ๋ฅผ messages์— ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค.

cursor ๋ฐฉ์‹์€ ์ƒˆ๋กœ ํ˜ธ์ถœํ•  ๊ฐ’๋งŒํผ๋งŒ ๊ฐ€์ ธ์˜ค๊ธฐ ๋•Œ๋ฌธ์— ํ•„ํ„ฐ๋ง์ด ํ•„์š”ํ•œ์ง€ ๊ณ ๋ฏผ์ด ๋“ค์—ˆ๋Š”๋ฐ, ๋งŒ์•ฝ ์‹ค์‹œ๊ฐ„์œผ๋กœ ๋ณ€๊ฒฝ๋˜๋Š” ๋ฐ์ดํ„ฐ๊ฐ€ ์žˆ๋‹ค๋ฉด ํ•„ํ„ฐ๋ง์ด ํ•„์š”ํ•˜์ง€ ์•Š์€๊ฐ€? ๋ผ๊ณ  ์ƒ๊ฐํ•ด์„œ ์ชฝ์ง€์˜ messageId ๊ฐ’์„ ๊ธฐ์ค€์œผ๋กœ ํ•„ํ„ฐ๋งํ•ด์„œ ์ค‘๋ณต์„ ์ œ๊ฑฐํ–ˆ์Šต๋‹ˆ๋‹ค.

์ด๋ ‡๊ฒŒ ํ•ด์„œ updatedMessageList์—๋Š” ์ค‘๋ณต๋˜์ง€ ์•Š์€ ๋ฉ”์‹œ์ง€๋งŒ ํฌํ•จ๋ฉ๋‹ˆ๋‹ค.

 

 

 

 ๊ธฐ์กด 1์ฐจ ๊ฐœ๋ฐœ๋ฌผ์— ์กด์žฌํ•˜๋˜ react-cool-inview ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์˜ useInView๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋ทฐ์— ๋“ค์–ด์™”์„ ๋•Œ ํ˜ธ์ถœ๋˜๋Š” onEnter ์ฝœ๋ฐฑ์„ ์„ค์ •ํ–ˆ์Šต๋‹ˆ๋‹ค. ๋กœ๋”ฉ ์ƒํƒœ๊ฐ€ ์•„๋‹Œ ๊ฒฝ์šฐ์™€ data.nextCursor๊ฐ€ ์กด์žฌํ•  ๊ฒฝ์šฐ๋ฅผ ๋ชจ๋‘ ๋งŒ์กฑํ•  ๋•Œ ํ•ด๋‹น ์ปค์„œ๋ฅผ ์„ค์ •ํ•ด์„œ ๋‹ค์Œ ๋ฉ”์‹œ์ง€ ํŽ˜์ด์ง€๋ฅผ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์žˆ๋„๋ก ํ–ˆ์Šต๋‹ˆ๋‹ค.

 ๋‹ค์Œ๊ณผ ๊ฐ™์ด observe๋ฅผ ๋ฐ์ดํ„ฐ ๋ฆฌ์ŠคํŠธ๋ฅผ ๋ถˆ๋Ÿฌ์˜ฌ ์ปดํฌ๋„ŒํŠธ์— props๋กœ ๋“ฑ๋กํ•ด์„œ ํ•ด๋‹น ์ปดํฌ๋„ŒํŠธ์˜ ๋งˆ์ง€๋ง‰ ๋ทฐํฌํŠธ์— ์ง„์ž…๋˜์—ˆ์„ ๋•Œ setCursor์— data์˜ nextCursor๊ฐ’์„ ํ• ๋‹นํ•˜๋„๋ก ํ–ˆ์Šต๋‹ˆ๋‹ค.

 

  const { observe } = useInView({
    onEnter: () => {
      if (hasMore && !isLoading && data?.nextCursor) {
        setCursor(data.nextCursor)
      } 
    },
  })
  
  return (
    <div>
      {messageCount > 0 ? (
         <MessageList
           messages={messages}
           userId={userId}
           messageType={messageType}
           observe={observe}
          />
            ) : (
              <div>
                ...
              </div>
            )}
          </div>
  )
}

 

https://youtu.be/gUQKzlbdzQg

 

'What I Learn' ์นดํ…Œ๊ณ ๋ฆฌ์˜ ๋‹ค๋ฅธ ๊ธ€

Tooltip ๊ตฌํ˜„  (0) 2024.12.10
Lottie๋กœ ์ชฝ์ง€ ์ธ๋„ค์ผ์— ์ ์šฉํ•˜๊ธฐ  (0) 2024.12.10
Toast ์ ์šฉ๊ธฐ  (0) 2024.12.10
scroll ์ด๋ฒคํŠธ๋ฅผ ์‚ฌ์šฉํ•œ ๋ฌดํ•œ ์Šคํฌ๋กค ๊ตฌํ˜„  (1) 2024.10.27
Next.js ๋ณ‘๋ ฌ ๋ผ์šฐํŒ… & ๊ฒฝ๋กœ ๊ฐ€๋กœ์ฑ„๊ธฐ๋ฅผ ํ†ตํ•œ ๋ชจ๋‹ฌ ํŽ˜์ด์ง€ ๊ตฌํ˜„  (1) 2024.10.27
'What I Learn' ์นดํ…Œ๊ณ ๋ฆฌ์˜ ๋‹ค๋ฅธ ๊ธ€
  • Tooltip ๊ตฌํ˜„
  • Lottie๋กœ ์ชฝ์ง€ ์ธ๋„ค์ผ์— ์ ์šฉํ•˜๊ธฐ
  • Toast ์ ์šฉ๊ธฐ
  • scroll ์ด๋ฒคํŠธ๋ฅผ ์‚ฌ์šฉํ•œ ๋ฌดํ•œ ์Šคํฌ๋กค ๊ตฌํ˜„
nuew
nuew
๐Ÿคธ ์žฌ์ฃผ ๋„˜๋Š” ์ค‘
  • nuew
    bloggg. . .๐Ÿฆ–๐Ÿ’ฅ
    nuew
  • ์ „์ฒด
    ์˜ค๋Š˜
    ์–ด์ œ
    • ๋ถ„๋ฅ˜ ์ „์ฒด๋ณด๊ธฐ (88)
      • issue (10)
      • baekjoon (41)
      • lecture recap (11)
      • What I Learn (26)
      • retrospective (0)
      • maeil-mail (0)
  • ๋ธ”๋กœ๊ทธ ๋ฉ”๋‰ด

    • ํ™ˆ
    • ํƒœ๊ทธ
    • ๋ฐฉ๋ช…๋ก
  • ๋งํฌ

  • ๊ณต์ง€์‚ฌํ•ญ

  • ์ธ๊ธฐ ๊ธ€

  • ํƒœ๊ทธ

    js
    zustand
    Baekjoon
    media-query
    ํ•œ์ž…ํฌ๊ธฐ๋กœ ์ž˜๋ผ๋จน๋Š” ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ
    ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ
    ์ฝ”๋”ฉํ…Œ์ŠคํŠธ
    JavaScript
    css
    ํ•œ์ž…ํฌ๊ธฐ๋กœ์ž˜๋ผ๋จน๋Š”ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ
    Study
    TailwindCSS
    issue
    Algorithm
    modal
    ๋ฐฑ์ค€
    Node.js
    ์•Œ๊ณ ๋ฆฌ์ฆ˜
    TypeScript
    what i learn
  • ์ตœ๊ทผ ๋Œ“๊ธ€

  • ์ตœ๊ทผ ๊ธ€

  • hELLOยท Designed By์ •์ƒ์šฐ.v4.10.3
nuew
TanStack Query์™€ ํ•จ๊ป˜ ๊ตฌํ˜„ํ•œ cursor ๋ฐฉ์‹์˜ ๋ฌดํ•œ ์Šคํฌ๋กค
์ƒ๋‹จ์œผ๋กœ

ํ‹ฐ์Šคํ† ๋ฆฌํˆด๋ฐ”