@@ -2,30 +2,42 @@ import { useQuery, useApolloClient } from '@apollo/client'
2
2
import { SSR } from '../lib/constants'
3
3
import { GET_NEW_COMMENTS , COMMENT_WITH_NEW } from '../fragments/comments'
4
4
import { ITEM_FULL } from '../fragments/items'
5
- import { useCallback , useEffect , useState , useRef } from 'react'
5
+ import { useCallback , useEffect , useState } from 'react'
6
6
import styles from './comment.module.css'
7
7
8
8
const POLL_INTERVAL = 1000 * 10 // 10 seconds
9
9
10
+ // the item query is used to update the item's newComments field
10
11
function itemUpdateQuery ( client , id , sort , fn ) {
11
12
client . cache . updateQuery ( {
12
13
query : ITEM_FULL ,
13
14
variables : sort === 'top' ? { id } : { id, sort }
14
- } , ( data ) => fn ( data ) )
15
+ } , ( data ) => {
16
+ if ( ! data ) return data
17
+ return { item : fn ( data . item ) }
18
+ } )
15
19
}
16
20
17
21
function commentUpdateFragment ( client , id , fn ) {
18
22
client . cache . updateFragment ( {
19
23
id : `Item:${ id } ` ,
20
24
fragment : COMMENT_WITH_NEW ,
21
25
fragmentName : 'CommentWithNew'
22
- } , ( data ) => fn ( data ) )
26
+ } , ( data ) => {
27
+ if ( ! data ) return data
28
+ return { ...data , ...fn ( data ) }
29
+ } )
30
+ }
31
+
32
+ function dedupeComments ( existing = [ ] , incoming = [ ] ) {
33
+ const existingIds = new Set ( existing . map ( c => c . id ) )
34
+ return [ ...incoming . filter ( c => ! existingIds . has ( c . id ) ) , ...existing ]
23
35
}
24
36
25
37
export default function useLiveComments ( rootId , after , sort ) {
26
38
const client = useApolloClient ( )
27
39
const [ latest , setLatest ] = useState ( after )
28
- const queuedCommentsRef = useRef ( [ ] )
40
+ const [ queue , setQueue ] = useState ( [ ] )
29
41
30
42
const { data } = useQuery ( GET_NEW_COMMENTS , SSR
31
43
? { }
@@ -39,11 +51,11 @@ export default function useLiveComments (rootId, after, sort) {
39
51
40
52
// live comments can be orphans if the parent comment is not in the cache
41
53
// queue them up and retry later, when the parent decides they want the children.
42
- const allComments = [ ...queuedCommentsRef . current , ...data . newComments . comments ]
54
+ const allComments = [ ...queue , ...data . newComments . comments ]
43
55
const { queuedComments } = cacheNewComments ( client , rootId , allComments , sort )
44
56
45
- // keep the queued comments in the ref for the next poll
46
- queuedCommentsRef . current = queuedComments
57
+ // keep the queued comments for the next poll
58
+ setQueue ( queuedComments )
47
59
48
60
// update latest timestamp to the latest comment created at
49
61
setLatest ( prevLatest => getLatestCommentCreatedAt ( data . newComments . comments , prevLatest ) )
@@ -61,10 +73,7 @@ function cacheNewComments (client, rootId, newComments, sort) {
61
73
// if the comment is a top level comment, update the item
62
74
if ( topLevel ) {
63
75
console . log ( 'topLevel' , topLevel )
64
- itemUpdateQuery ( client , rootId , sort , ( data ) => {
65
- if ( ! data ) return data
66
- return { item : mergeNewComment ( data ?. item , newComment ) }
67
- } )
76
+ itemUpdateQuery ( client , rootId , sort , ( data ) => mergeNewComment ( data , newComment ) )
68
77
} else {
69
78
// check if parent exists in cache before attempting update
70
79
const parentExists = client . cache . readFragment ( {
@@ -76,10 +85,7 @@ function cacheNewComments (client, rootId, newComments, sort) {
76
85
if ( parentExists ) {
77
86
// if the comment is a reply, update the parent comment
78
87
console . log ( 'reply' , parentId )
79
- commentUpdateFragment ( client , parentId , ( data ) => {
80
- if ( ! data ) return data
81
- return mergeNewComment ( data , newComment )
82
- } )
88
+ commentUpdateFragment ( client , parentId , ( data ) => mergeNewComment ( data , newComment ) )
83
89
} else {
84
90
// parent not in cache, queue for retry
85
91
queuedComments . push ( newComment )
@@ -93,6 +99,7 @@ function cacheNewComments (client, rootId, newComments, sort) {
93
99
// merge new comment into item's newComments
94
100
// if the new comment is already in item's newComments or existing comments, do nothing
95
101
function mergeNewComment ( item , newComment ) {
102
+ console . log ( 'mergeNewComment' , item , newComment )
96
103
const existingNewComments = item . newComments || [ ]
97
104
const existingComments = item . comments ?. comments || [ ]
98
105
@@ -119,47 +126,24 @@ export function ShowNewComments ({ newComments = [], itemId, topLevel = false, s
119
126
const client = useApolloClient ( )
120
127
121
128
const showNewComments = useCallback ( ( ) => {
129
+ const payload = ( data ) => {
130
+ if ( ! data ) return data
131
+ return {
132
+ ...data ,
133
+ comments : { ...data . comments , comments : dedupeComments ( data . comments . comments , newComments ) } ,
134
+ newComments : [ ]
135
+ }
136
+ }
137
+
122
138
if ( topLevel ) {
123
139
console . log ( 'topLevel' , topLevel )
124
- itemUpdateQuery ( client , itemId , sort , ( data ) => {
125
- console . log ( 'data' , data )
126
- if ( ! data ) return data
127
- const { item } = data
128
-
129
- return {
130
- item : {
131
- ...item ,
132
- comments : injectComments ( item . comments , newComments ) ,
133
- newComments : [ ]
134
- }
135
- }
136
- } )
140
+ itemUpdateQuery ( client , itemId , sort , payload )
137
141
} else {
138
142
console . log ( 'reply' , itemId )
139
- commentUpdateFragment ( client , itemId , ( data ) => {
140
- console . log ( 'data' , data )
141
- if ( ! data ) return data
142
-
143
- return {
144
- ...data ,
145
- comments : injectComments ( data . comments , newComments ) ,
146
- newComments : [ ]
147
- }
148
- } )
143
+ commentUpdateFragment ( client , itemId , payload )
149
144
}
150
145
} , [ client , itemId , newComments , topLevel , sort ] )
151
146
152
- // inject new comments into existing comments
153
- // if the new comment is already in existing comments, do nothing
154
- const injectComments = ( existingComments = [ ] , newComments = [ ] ) => {
155
- const existingIds = new Set ( existingComments . comments . map ( c => c . id ) )
156
- const filteredNew = newComments . filter ( c => ! existingIds . has ( c . id ) )
157
- return {
158
- ...existingComments ,
159
- comments : [ ...filteredNew , ...( existingComments . comments || [ ] ) ]
160
- }
161
- }
162
-
163
147
return (
164
148
< div
165
149
onClick = { showNewComments }
0 commit comments