Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[C-5807] Add comment normalization #11484

Merged
merged 9 commits into from
Feb 28, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/common/src/api/tan-query/comments/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ export * from './utils'
export * from './useUserComments'
export * from './useTrackComments'
export * from './useComment'
export * from './useComments'
export * from './useTrackCommentCount'
export * from './useCommentReplies'
export * from './usePostComment'
Expand Down
49 changes: 25 additions & 24 deletions packages/common/src/api/tan-query/comments/useCommentReplies.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,26 +6,27 @@ import { useDispatch } from 'react-redux'

import { replyCommentFromSDK, transformAndCleanList } from '~/adapters'
import { useAudiusQueryContext } from '~/audius-query'
import { Comment, Feature, ID, ReplyComment } from '~/models'
import { Comment, Feature, ID } from '~/models'
import { toast } from '~/store/ui/toast/slice'

import { QueryOptions } from '../types'
import { useCurrentUserId } from '../useCurrentUserId'
import { primeCommentData } from '../utils/primeCommentData'
import { primeRelatedData } from '../utils/primeRelatedData'

import { COMMENT_REPLIES_PAGE_SIZE, messages } from './types'
import { useComments } from './useComments'
import { getCommentQueryKey, getCommentRepliesQueryKey } from './utils'

export type GetRepliesArgs = {
commentId: ID
enabled?: boolean
pageSize?: number
}

export const useCommentReplies = ({
commentId,
enabled,
pageSize = COMMENT_REPLIES_PAGE_SIZE
}: GetRepliesArgs) => {
export const useCommentReplies = (
{ commentId, pageSize = COMMENT_REPLIES_PAGE_SIZE }: GetRepliesArgs,
options?: QueryOptions
) => {
const { audiusSdk, reportToSentry } = useAudiusQueryContext()
const queryClient = useQueryClient()
const dispatch = useDispatch()
Expand All @@ -34,13 +35,12 @@ export const useCommentReplies = ({

const queryRes = useInfiniteQuery({
queryKey: getCommentRepliesQueryKey({ commentId, pageSize }),
enabled: !!enabled,
initialPageParam: startingLimit,
getNextPageParam: (lastPage: ReplyComment[], pages) => {
getNextPageParam: (lastPage: ID[], pages) => {
if (lastPage?.length < pageSize) return undefined
return (pages.length ?? pageSize) * pageSize + startingLimit
},
queryFn: async ({ pageParam }): Promise<ReplyComment[]> => {
queryFn: async ({ pageParam }): Promise<ID[]> => {
const sdk = await audiusSdk()
const response = await sdk.full.comments.getCommentReplies({
commentId: Id.parse(commentId),
Expand All @@ -49,33 +49,32 @@ export const useCommentReplies = ({
offset: pageParam
})

const replyList = transformAndCleanList(
response.data,
replyCommentFromSDK
)
const replies = transformAndCleanList(response.data, replyCommentFromSDK)

primeRelatedData({ related: response.related, queryClient, dispatch })

// Update the parent comment with the new replies and prime the reply data
// Add the replies to our parent comment replies list
queryClient.setQueryData(
getCommentQueryKey(commentId),
(comment: Comment | undefined) =>
({
...comment,
replies: [...(comment?.replies ?? []), ...replyList]
replies: [...(comment?.replies ?? []), ...replies]
}) as Comment
)
// Put each reply into their individual comment cache
replyList.forEach((comment) => {
queryClient.setQueryData(getCommentQueryKey(comment.id), comment)
})
return replyList

// Prime each reply in the cache
primeCommentData({ comments: replies, queryClient })

// Return just the IDs for the infinite query
return replies.map((reply) => reply.id)
},
staleTime: Infinity,
gcTime: 1
select: (data) => data.pages.flat(),
...options
})

const { error } = queryRes
const { error, data: replyIds } = queryRes

useEffect(() => {
if (error) {
Expand All @@ -88,5 +87,7 @@ export const useCommentReplies = ({
}
}, [error, dispatch, reportToSentry])

return { ...queryRes, data: queryRes.data?.pages?.flat() ?? [] }
const { data: replies } = useComments(replyIds)

return { ...queryRes, data: replies }
}
48 changes: 48 additions & 0 deletions packages/common/src/api/tan-query/comments/useComments.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { useMemo } from 'react'

import { useQueries, useQueryClient } from '@tanstack/react-query'
import { keyBy } from 'lodash'

import { ID } from '~/models/Identifiers'

import { QueryOptions } from '../types'
import { combineQueryResults } from '../utils/combineQueryResults'

import { CommentOrReply } from './types'
import { getCommentQueryKey } from './utils'

export const getCommentsQueryKey = (commentIds: ID[] | null | undefined) => [
'comments',
commentIds
]

export const useComments = (
commentIds: ID[] | null | undefined,
options?: QueryOptions
) => {
const queryClient = useQueryClient()

const { data: comments, ...queryResults } = useQueries({
queries: (commentIds ?? []).map((commentId) => ({
queryKey: getCommentQueryKey(commentId),
queryFn: async (): Promise<CommentOrReply | {}> => {
// Comments are expected to be pre-populated in the cache from other queries
return queryClient.getQueryData(getCommentQueryKey(commentId)) ?? {}
},
...options,
enabled: options?.enabled !== false && !!commentId
})),
combine: combineQueryResults<CommentOrReply[]>
})

const byId = useMemo(() => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this common in the other hooks that do this? Or what are we trying to solve here?

const byId = keyBy(comments, 'id')
return byId
}, [comments])

return {
data: comments,
byId,
...queryResults
}
}
57 changes: 27 additions & 30 deletions packages/common/src/api/tan-query/comments/useTrackComments.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,31 +13,36 @@ import { useAudiusQueryContext } from '~/audius-query'
import { Feature, ID } from '~/models'
import { toast } from '~/store/ui/toast/slice'

import { QueryOptions } from '../types'
import { useCurrentUserId } from '../useCurrentUserId'
import { primeCommentData } from '../utils/primeCommentData'
import { primeRelatedData } from '../utils/primeRelatedData'

import { COMMENT_ROOT_PAGE_SIZE, CommentOrReply, messages } from './types'
import { getCommentQueryKey, getTrackCommentListQueryKey } from './utils'
import { COMMENT_ROOT_PAGE_SIZE, messages } from './types'
import { useComments } from './useComments'
import { getTrackCommentListQueryKey } from './utils'

export type GetCommentsByTrackArgs = {
trackId: ID
userId: ID | null
sortMethod: any
pageSize?: number
}

export const useTrackComments = ({
trackId,
userId,
sortMethod,
pageSize = COMMENT_ROOT_PAGE_SIZE
}: GetCommentsByTrackArgs) => {
export const useTrackComments = (
{
trackId,
sortMethod,
pageSize = COMMENT_ROOT_PAGE_SIZE
}: GetCommentsByTrackArgs,
options?: QueryOptions
) => {
const { audiusSdk, reportToSentry } = useAudiusQueryContext()
const isMutating = useIsMutating()
const queryClient = useQueryClient()
const dispatch = useDispatch()
const { data: currentUserId } = useCurrentUserId()

const queryRes = useInfiniteQuery({
enabled: !!trackId && trackId !== 0 && isMutating === 0,
initialPageParam: 0,
getNextPageParam: (lastPage: ID[], pages) => {
if (lastPage?.length < pageSize) return undefined
Expand All @@ -51,8 +56,7 @@ export const useTrackComments = ({
offset: pageParam,
limit: pageSize,
sortMethod,
// TODO: why is this toString instead of encode
userId: userId?.toString() ?? undefined
userId: currentUserId?.toString()
})

const commentList = transformAndCleanList(
Expand All @@ -62,27 +66,18 @@ export const useTrackComments = ({

primeRelatedData({ related: commentsRes.related, queryClient, dispatch })

// Populate individual comment cache
commentList.forEach((comment) => {
queryClient.setQueryData<CommentOrReply>(
getCommentQueryKey(comment.id),
comment
)
comment?.replies?.forEach?.((reply) =>
queryClient.setQueryData<CommentOrReply>(
getCommentQueryKey(reply.id),
reply
)
)
})
// For the comment list cache, we only store the ids of the comments (organized by sort method)
// Prime comment data in the cache
primeCommentData({ comments: commentList, queryClient })

// Return just the IDs for the infinite query
return commentList.map((comment) => comment.id)
},
staleTime: Infinity, // Stale time is set to infinity so that we never reload data thats currently shown on screen (because sorting could have changed)
gcTime: 0 // Cache time is set to 1 so that the data is cleared any time we leave the page viewing it or change sorts
select: (data) => data.pages.flat(),
...options,
enabled: isMutating === 0 && options?.enabled !== false
})

const { error } = queryRes
const { error, data: commentIds } = queryRes

useEffect(() => {
if (error) {
Expand All @@ -95,5 +90,7 @@ export const useTrackComments = ({
}
}, [error, dispatch, reportToSentry])

return { ...queryRes, data: queryRes.data?.pages?.flat() ?? [] }
const { data: comments } = useComments(commentIds)

return { ...queryRes, data: comments }
}
53 changes: 26 additions & 27 deletions packages/common/src/api/tan-query/comments/useUserComments.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,27 +13,31 @@ import { useAudiusQueryContext } from '~/audius-query'
import { Feature, ID } from '~/models'
import { toast } from '~/store/ui/toast/slice'

import { QueryOptions } from '../types'
import { useCurrentUserId } from '../useCurrentUserId'
import { primeCommentData } from '../utils/primeCommentData'
import { primeRelatedData } from '../utils/primeRelatedData'

import { COMMENT_ROOT_PAGE_SIZE, CommentOrReply, messages } from './types'
import { getCommentQueryKey } from './utils'
import { COMMENT_ROOT_PAGE_SIZE, messages } from './types'
import { useComments } from './useComments'

export const useUserComments = ({
userId,
pageSize = COMMENT_ROOT_PAGE_SIZE
}: {
userId: ID | null
pageSize?: number
}) => {
export const useUserComments = (
{
userId,
pageSize = COMMENT_ROOT_PAGE_SIZE
}: {
userId: ID | null
pageSize?: number
},
options?: QueryOptions
) => {
const { audiusSdk, reportToSentry } = useAudiusQueryContext()
const { data: currentUserId } = useCurrentUserId()
const isMutating = useIsMutating()
const queryClient = useQueryClient()
const dispatch = useDispatch()

const queryRes = useInfiniteQuery({
enabled: !!userId && userId !== 0 && isMutating === 0,
initialPageParam: 0,
getNextPageParam: (lastPage: ID[], pages) => {
if (lastPage?.length < pageSize) return undefined
Expand All @@ -56,25 +60,18 @@ export const useUserComments = ({

primeRelatedData({ related: commentsRes.related, queryClient, dispatch })

// Populate individual comment cache
commentList.forEach((comment) => {
queryClient.setQueryData<CommentOrReply>(
getCommentQueryKey(comment.id),
comment
)
comment?.replies?.forEach?.((reply) =>
queryClient.setQueryData<CommentOrReply>(
getCommentQueryKey(reply.id),
reply
)
)
})
// For the comment list cache, we only store the ids of the comments (organized by sort method)
// Prime comment data in the cache
primeCommentData({ comments: commentList, queryClient })

// Return just the IDs for the infinite query
return commentList.map((comment) => comment.id)
}
},
select: (data) => data.pages.flat(),
...options,
enabled: isMutating === 0 && options?.enabled !== false
})

const { error } = queryRes
const { error, data: commentIds } = queryRes

useEffect(() => {
if (error) {
Expand All @@ -87,5 +84,7 @@ export const useUserComments = ({
}
}, [error, dispatch, reportToSentry])

return { ...queryRes, data: queryRes.data?.pages?.flat() ?? [] }
const { data: comments } = useComments(commentIds)

return { ...queryRes, data: comments }
}
36 changes: 36 additions & 0 deletions packages/common/src/api/tan-query/utils/primeCommentData.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { QueryClient } from '@tanstack/react-query'

import { ReplyComment } from '~/models'

import { CommentOrReply } from '../comments/types'
import { getCommentQueryKey } from '../comments/utils'

/**
* Primes the comment data in the query cache
*/
export const primeCommentData = ({
comments,
queryClient
}: {
comments: CommentOrReply[]
queryClient: QueryClient
}) => {
// Populate individual comment cache
comments.forEach((comment) => {
// Prime the main comment
queryClient.setQueryData<CommentOrReply>(
getCommentQueryKey(comment.id),
comment
)

// Prime any replies if they exist
if ('replies' in comment && comment.replies) {
comment.replies.forEach((reply: ReplyComment) =>
queryClient.setQueryData<CommentOrReply>(
getCommentQueryKey(reply.id),
reply
)
)
}
})
}
Loading