์ด๋ฒ ํ๋ก์ ํธ์์๋ ๋ชจ๋ฐ์ผ ์ฌ์ฉ์๋ฅผ ํ๊ฒ์ผ๋ก ์งํํ๊ธฐ์ ๋ชจ๋ฐ์ผ์์ ๋ ์ฌ์ฉ์ฑ์ด ์ข์ ๋ฌดํ ์คํฌ๋กค์ ๊ตฌํํ๊ฒ ๋์์ต๋๋ค. ์ด์ ์๋ 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>
)
}
'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 |