Skip to content

Commit 40d56fe

Browse files
committed
refactor: ShowNewComments is its own component; cleanup: proven useless dedupe on ShowNewComments, count nested ncomments from fresh new comments
1 parent f710457 commit 40d56fe

File tree

4 files changed

+67
-63
lines changed

4 files changed

+67
-63
lines changed

components/comment.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ import LinkToContext from './link-to-context'
2828
import Boost from './boost-button'
2929
import { gql, useApolloClient } from '@apollo/client'
3030
import classNames from 'classnames'
31-
import { ShowNewComments } from './use-live-comments'
31+
import { ShowNewComments } from './show-new-comments'
3232

3333
function Parent ({ item, rootText }) {
3434
const root = useRoot()

components/comments.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ import { defaultCommentSort } from '@/lib/item'
88
import { useRouter } from 'next/router'
99
import MoreFooter from './more-footer'
1010
import { FULL_COMMENTS_THRESHOLD } from '@/lib/constants'
11-
import useLiveComments, { ShowNewComments } from './use-live-comments'
11+
import useLiveComments from './use-live-comments'
12+
import { ShowNewComments } from './show-new-comments'
1213

1314
export function CommentsHeader ({ handleSort, pinned, bio, parentCreatedAt, commentSats }) {
1415
const router = useRouter()

components/show-new-comments.js

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import { useCallback } from 'react'
2+
import { useApolloClient } from '@apollo/client'
3+
import { COMMENT_WITH_NEW } from '../fragments/comments'
4+
import styles from './comment.module.css'
5+
import { itemUpdateQuery, commentUpdateFragment } from './use-live-comments'
6+
7+
function prepareComments (client, newComments) {
8+
return (data) => {
9+
// TODO: it might be sane to pass the cache ref to the ShowNewComments component
10+
// TODO: and use it to read the latest newComments from the cache
11+
// newComments can have themselves new comments between the time the button is clicked and the query is executed
12+
// so we need to read the latest newComments from the cache
13+
const freshNewComments = newComments.map(c => {
14+
const fragment = client.cache.readFragment({
15+
id: `Item:${c.id}`,
16+
fragment: COMMENT_WITH_NEW,
17+
fragmentName: 'CommentWithNew'
18+
})
19+
// if the comment is not in the cache, return the original comment
20+
return fragment || c
21+
})
22+
23+
// count the total number of comments including nested comments
24+
let ncomments = data.ncomments + freshNewComments.length
25+
for (const comment of freshNewComments) {
26+
ncomments += (comment.ncomments || 0)
27+
}
28+
29+
return {
30+
...data,
31+
comments: { ...data.comments, comments: [...freshNewComments, ...data.comments.comments] },
32+
ncomments,
33+
newComments: []
34+
}
35+
}
36+
}
37+
38+
// ShowNewComments is a component that dedupes, refreshes and injects newComments into the comments field
39+
export function ShowNewComments ({ newComments = [], itemId, topLevel = false, sort }) {
40+
const client = useApolloClient()
41+
42+
const showNewComments = useCallback(() => {
43+
const payload = prepareComments(client, newComments)
44+
45+
if (topLevel) {
46+
itemUpdateQuery(client, itemId, sort, payload)
47+
} else {
48+
commentUpdateFragment(client, itemId, payload)
49+
}
50+
}, [client, itemId, newComments, topLevel, sort])
51+
52+
return (
53+
<div
54+
onClick={showNewComments}
55+
className={`${topLevel && `d-block fw-bold ${styles.comment} pb-2`} d-flex align-items-center gap-2 px-3 pointer`}
56+
>
57+
{newComments.length > 1 ? `${newComments.length} new comments` : 'show new comment'}
58+
<div className={styles.newCommentDot} />
59+
</div>
60+
)
61+
}

components/use-live-comments.js

Lines changed: 3 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,7 @@ import { useQuery, useApolloClient } from '@apollo/client'
22
import { SSR } from '../lib/constants'
33
import { GET_NEW_COMMENTS, COMMENT_WITH_NEW } from '../fragments/comments'
44
import { ITEM_FULL } from '../fragments/items'
5-
import { useCallback, useEffect, useRef, useState } from 'react'
6-
import styles from './comment.module.css'
5+
import { useEffect, useRef, useState } from 'react'
76

87
const POLL_INTERVAL = 1000 * 10 // 10 seconds
98

@@ -40,7 +39,7 @@ export default function useLiveComments (rootId, after, sort) {
4039
}
4140

4241
// the item query is used to update the item's newComments field
43-
function itemUpdateQuery (client, id, sort, fn) {
42+
export function itemUpdateQuery (client, id, sort, fn) {
4443
client.cache.updateQuery({
4544
query: ITEM_FULL,
4645
// updateQuery needs the correct variables to update the correct item
@@ -53,7 +52,7 @@ function itemUpdateQuery (client, id, sort, fn) {
5352
}
5453

5554
// update the newComments field of a nested comment fragment
56-
function commentUpdateFragment (client, id, fn) {
55+
export function commentUpdateFragment (client, id, fn) {
5756
client.cache.updateFragment({
5857
id: `Item:${id}`,
5958
fragment: COMMENT_WITH_NEW,
@@ -122,60 +121,3 @@ function getLatestCommentCreatedAt (comments, latest) {
122121
// convert back to ISO string
123122
return new Date(maxTimestamp).toISOString()
124123
}
125-
126-
// ShowNewComments is a component that dedupes, refreshes and injects newComments into the comments field
127-
export function ShowNewComments ({ newComments = [], itemId, topLevel = false, sort }) {
128-
const client = useApolloClient()
129-
130-
const showNewComments = useCallback(() => {
131-
const payload = (data) => {
132-
// TODO: it might be sane to pass the cache ref to the ShowNewComments component
133-
// TODO: and use it to read the latest newComments from the cache
134-
// newComments can have themselves new comments between the time the button is clicked and the query is executed
135-
// so we need to read the latest newComments from the cache
136-
const freshNewComments = newComments.map(c => {
137-
const fragment = client.cache.readFragment({
138-
id: `Item:${c.id}`,
139-
fragment: COMMENT_WITH_NEW,
140-
fragmentName: 'CommentWithNew'
141-
})
142-
// if the comment is not in the cache, return the original comment
143-
return fragment || c
144-
})
145-
146-
// deduplicate the fresh new comments with the existing comments
147-
const dedupedComments = dedupeComments(data.comments.comments, freshNewComments)
148-
149-
return {
150-
...data,
151-
comments: { ...data.comments, comments: dedupedComments },
152-
ncomments: data.ncomments + (dedupedComments.length || 0),
153-
newComments: []
154-
}
155-
}
156-
157-
if (topLevel) {
158-
itemUpdateQuery(client, itemId, sort, payload)
159-
} else {
160-
commentUpdateFragment(client, itemId, payload)
161-
}
162-
}, [client, itemId, newComments, topLevel, sort])
163-
164-
return (
165-
<div
166-
onClick={showNewComments}
167-
className={`${topLevel && `d-block fw-bold ${styles.comment} pb-2`} d-flex align-items-center gap-2 px-3 pointer`}
168-
>
169-
{newComments.length > 1 ? `${newComments.length} new comments` : 'show new comment'}
170-
<div className={styles.newCommentDot} />
171-
</div>
172-
)
173-
}
174-
175-
// even though we already deduplicated comments during the newComments merge,
176-
// refetches, client-side navigation, etc. can cause duplicates to appear,
177-
// so we'll make sure to deduplicate them here, by id
178-
function dedupeComments (existing = [], incoming = []) {
179-
const existingIds = new Set(existing.map(c => c.id))
180-
return [...incoming.filter(c => !existingIds.has(c.id)), ...existing]
181-
}

0 commit comments

Comments
 (0)