Change Tipper interface, memoize FeedComment (#1000)
				
					
				
			* Change `Tipper` interface, memoize `FeedComment` * Fixup per James feedback * More fixup per James feedback
This commit is contained in:
		
							parent
							
								
									d2273087cf
								
							
						
					
					
						commit
						ddb186dd98
					
				|  | @ -2,6 +2,7 @@ import { Answer } from 'common/answer' | |||
| import { FreeResponseContract } from 'common/contract' | ||||
| import { ContractComment } from 'common/comment' | ||||
| import React, { useEffect, useRef, useState } from 'react' | ||||
| import { sum } from 'lodash' | ||||
| import { Col } from 'web/components/layout/col' | ||||
| import { Row } from 'web/components/layout/row' | ||||
| import { Avatar } from 'web/components/avatar' | ||||
|  | @ -14,6 +15,8 @@ import { | |||
| } from 'web/components/feed/feed-comments' | ||||
| import { CopyLinkDateTimeComponent } from 'web/components/feed/copy-link-date-time' | ||||
| import { useRouter } from 'next/router' | ||||
| import { useUser } from 'web/hooks/use-user' | ||||
| import { useEvent } from 'web/hooks/use-event' | ||||
| import { CommentTipMap } from 'web/hooks/use-tip-txns' | ||||
| import { UserLink } from 'web/components/user-link' | ||||
| 
 | ||||
|  | @ -27,11 +30,17 @@ export function FeedAnswerCommentGroup(props: { | |||
|   const { username, avatarUrl, name, text } = answer | ||||
| 
 | ||||
|   const [replyTo, setReplyTo] = useState<ReplyTo>() | ||||
|   const user = useUser() | ||||
|   const router = useRouter() | ||||
|   const answerElementId = `answer-${answer.id}` | ||||
|   const highlighted = router.asPath.endsWith(`#${answerElementId}`) | ||||
|   const answerRef = useRef<HTMLDivElement>(null) | ||||
| 
 | ||||
|   const onSubmitComment = useEvent(() => setReplyTo(undefined)) | ||||
|   const onReplyClick = useEvent((comment: ContractComment) => { | ||||
|     setReplyTo({ id: comment.id, username: comment.userUsername }) | ||||
|   }) | ||||
| 
 | ||||
|   useEffect(() => { | ||||
|     if (highlighted && answerRef.current != null) { | ||||
|       answerRef.current.scrollIntoView(true) | ||||
|  | @ -95,10 +104,10 @@ export function FeedAnswerCommentGroup(props: { | |||
|             indent={true} | ||||
|             contract={contract} | ||||
|             comment={comment} | ||||
|             tips={tips[comment.id] ?? {}} | ||||
|             onReplyClick={() => | ||||
|               setReplyTo({ id: comment.id, username: comment.userUsername }) | ||||
|             } | ||||
|             myTip={user ? tips[comment.id]?.[user.id] : undefined} | ||||
|             totalTip={sum(Object.values(tips[comment.id] ?? {}))} | ||||
|             showTip={true} | ||||
|             onReplyClick={onReplyClick} | ||||
|           /> | ||||
|         ))} | ||||
|       </Col> | ||||
|  | @ -112,7 +121,7 @@ export function FeedAnswerCommentGroup(props: { | |||
|             contract={contract} | ||||
|             parentAnswerOutcome={answer.number.toString()} | ||||
|             replyTo={replyTo} | ||||
|             onSubmitComment={() => setReplyTo(undefined)} | ||||
|             onSubmitComment={onSubmitComment} | ||||
|           /> | ||||
|         </div> | ||||
|       )} | ||||
|  |  | |||
|  | @ -1,11 +1,14 @@ | |||
| import React, { memo, useEffect, useRef, useState } from 'react' | ||||
| import { Editor } from '@tiptap/react' | ||||
| import { useRouter } from 'next/router' | ||||
| import { sum } from 'lodash' | ||||
| import clsx from 'clsx' | ||||
| 
 | ||||
| import { ContractComment } from 'common/comment' | ||||
| import { Contract } from 'common/contract' | ||||
| import React, { useEffect, useRef, useState } from 'react' | ||||
| import { useUser } from 'web/hooks/use-user' | ||||
| import { formatMoney } from 'common/util/format' | ||||
| import { useRouter } from 'next/router' | ||||
| import { Row } from 'web/components/layout/row' | ||||
| import clsx from 'clsx' | ||||
| import { Avatar } from 'web/components/avatar' | ||||
| import { OutcomeLabel } from 'web/components/outcome-label' | ||||
| import { CopyLinkDateTimeComponent } from 'web/components/feed/copy-link-date-time' | ||||
|  | @ -14,9 +17,9 @@ import { createCommentOnContract } from 'web/lib/firebase/comments' | |||
| import { Col } from 'web/components/layout/col' | ||||
| import { track } from 'web/lib/service/analytics' | ||||
| import { Tipper } from '../tipper' | ||||
| import { CommentTipMap, CommentTips } from 'web/hooks/use-tip-txns' | ||||
| import { CommentTipMap } from 'web/hooks/use-tip-txns' | ||||
| import { useEvent } from 'web/hooks/use-event' | ||||
| import { Content } from '../editor' | ||||
| import { Editor } from '@tiptap/react' | ||||
| import { UserLink } from 'web/components/user-link' | ||||
| import { CommentInput } from '../comment-input' | ||||
| import { AwardBountyButton } from 'web/components/award-bounty-button' | ||||
|  | @ -32,6 +35,12 @@ export function FeedCommentThread(props: { | |||
|   const { contract, threadComments, tips, parentComment } = props | ||||
|   const [replyTo, setReplyTo] = useState<ReplyTo>() | ||||
| 
 | ||||
|   const user = useUser() | ||||
|   const onSubmitComment = useEvent(() => setReplyTo(undefined)) | ||||
|   const onReplyClick = useEvent((comment: ContractComment) => { | ||||
|     setReplyTo({ id: comment.id, username: comment.userUsername }) | ||||
|   }) | ||||
| 
 | ||||
|   return ( | ||||
|     <Col className="relative w-full items-stretch gap-3 pb-4"> | ||||
|       <span | ||||
|  | @ -44,10 +53,10 @@ export function FeedCommentThread(props: { | |||
|           indent={commentIdx != 0} | ||||
|           contract={contract} | ||||
|           comment={comment} | ||||
|           tips={tips[comment.id] ?? {}} | ||||
|           onReplyClick={() => | ||||
|             setReplyTo({ id: comment.id, username: comment.userUsername }) | ||||
|           } | ||||
|           myTip={user ? tips[comment.id]?.[user.id] : undefined} | ||||
|           totalTip={sum(Object.values(tips[comment.id] ?? {}))} | ||||
|           showTip={true} | ||||
|           onReplyClick={onReplyClick} | ||||
|         /> | ||||
|       ))} | ||||
|       {replyTo && ( | ||||
|  | @ -60,7 +69,7 @@ export function FeedCommentThread(props: { | |||
|             contract={contract} | ||||
|             parentCommentId={parentComment.id} | ||||
|             replyTo={replyTo} | ||||
|             onSubmitComment={() => setReplyTo(undefined)} | ||||
|             onSubmitComment={onSubmitComment} | ||||
|           /> | ||||
|         </Col> | ||||
|       )} | ||||
|  | @ -68,14 +77,17 @@ export function FeedCommentThread(props: { | |||
|   ) | ||||
| } | ||||
| 
 | ||||
| export function FeedComment(props: { | ||||
| export const FeedComment = memo(function FeedComment(props: { | ||||
|   contract: Contract | ||||
|   comment: ContractComment | ||||
|   tips?: CommentTips | ||||
|   showTip?: boolean | ||||
|   myTip?: number | ||||
|   totalTip?: number | ||||
|   indent?: boolean | ||||
|   onReplyClick?: () => void | ||||
|   onReplyClick?: (comment: ContractComment) => void | ||||
| }) { | ||||
|   const { contract, comment, tips, indent, onReplyClick } = props | ||||
|   const { contract, comment, myTip, totalTip, showTip, indent, onReplyClick } = | ||||
|     props | ||||
|   const { | ||||
|     text, | ||||
|     content, | ||||
|  | @ -180,12 +192,18 @@ export function FeedComment(props: { | |||
|           {onReplyClick && ( | ||||
|             <button | ||||
|               className="font-bold hover:underline" | ||||
|               onClick={onReplyClick} | ||||
|               onClick={() => onReplyClick(comment)} | ||||
|             > | ||||
|               Reply | ||||
|             </button> | ||||
|           )} | ||||
|           {tips && <Tipper comment={comment} tips={tips} />} | ||||
|           {showTip && ( | ||||
|             <Tipper | ||||
|               comment={comment} | ||||
|               myTip={myTip ?? 0} | ||||
|               totalTip={totalTip ?? 0} | ||||
|             /> | ||||
|           )} | ||||
|           {(contract.openCommentBounties ?? 0) > 0 && ( | ||||
|             <AwardBountyButton comment={comment} contract={contract} /> | ||||
|           )} | ||||
|  | @ -193,7 +211,7 @@ export function FeedComment(props: { | |||
|       </div> | ||||
|     </Row> | ||||
|   ) | ||||
| } | ||||
| }) | ||||
| 
 | ||||
| function CommentStatus(props: { | ||||
|   contract: Contract | ||||
|  |  | |||
|  | @ -1,10 +1,9 @@ | |||
| import { useEffect, useRef, useState } from 'react' | ||||
| import toast from 'react-hot-toast' | ||||
| import { debounce, sum } from 'lodash' | ||||
| import { debounce } from 'lodash' | ||||
| 
 | ||||
| import { Comment } from 'common/comment' | ||||
| import { User } from 'common/user' | ||||
| import { CommentTips } from 'web/hooks/use-tip-txns' | ||||
| import { useUser } from 'web/hooks/use-user' | ||||
| import { transact } from 'web/lib/firebase/api' | ||||
| import { track } from 'web/lib/service/analytics' | ||||
|  | @ -13,25 +12,27 @@ import { Row } from './layout/row' | |||
| import { LIKE_TIP_AMOUNT } from 'common/like' | ||||
| import { formatMoney } from 'common/util/format' | ||||
| 
 | ||||
| export function Tipper(prop: { comment: Comment; tips: CommentTips }) { | ||||
|   const { comment, tips } = prop | ||||
| export function Tipper(prop: { | ||||
|   comment: Comment | ||||
|   myTip: number | ||||
|   totalTip: number | ||||
| }) { | ||||
|   const { comment, myTip, totalTip } = prop | ||||
| 
 | ||||
|   const me = useUser() | ||||
|   const myId = me?.id ?? '' | ||||
|   const savedTip = tips[myId] ?? 0 | ||||
| 
 | ||||
|   const [localTip, setLocalTip] = useState(savedTip) | ||||
|   const [localTip, setLocalTip] = useState(myTip) | ||||
| 
 | ||||
|   // listen for user being set
 | ||||
|   const initialized = useRef(false) | ||||
|   useEffect(() => { | ||||
|     if (tips[myId] && !initialized.current) { | ||||
|       setLocalTip(tips[myId]) | ||||
|     if (myTip && !initialized.current) { | ||||
|       setLocalTip(myTip) | ||||
|       initialized.current = true | ||||
|     } | ||||
|   }, [tips, myId]) | ||||
|   }, [myTip]) | ||||
| 
 | ||||
|   const total = sum(Object.values(tips)) - savedTip + localTip | ||||
|   const total = totalTip - myTip + localTip | ||||
| 
 | ||||
|   // declare debounced function only on first render
 | ||||
|   const [saveTip] = useState(() => | ||||
|  | @ -73,7 +74,7 @@ export function Tipper(prop: { comment: Comment; tips: CommentTips }) { | |||
| 
 | ||||
|   const addTip = (delta: number) => { | ||||
|     setLocalTip(localTip + delta) | ||||
|     me && saveTip(me, comment, localTip - savedTip + delta) | ||||
|     me && saveTip(me, comment, localTip - myTip + delta) | ||||
|     toast(`You tipped ${comment.userName} ${formatMoney(LIKE_TIP_AMOUNT)}!`) | ||||
|   } | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,5 +1,6 @@ | |||
| import { track } from '@amplitude/analytics-browser' | ||||
| import { Editor } from '@tiptap/core' | ||||
| import { sum } from 'lodash' | ||||
| import clsx from 'clsx' | ||||
| import { PostComment } from 'common/comment' | ||||
| import { Post } from 'common/post' | ||||
|  | @ -109,6 +110,7 @@ export function PostComment(props: { | |||
|   const { text, content, userUsername, userName, userAvatarUrl, createdTime } = | ||||
|     comment | ||||
| 
 | ||||
|   const me = useUser() | ||||
|   const [highlighted, setHighlighted] = useState(false) | ||||
|   const router = useRouter() | ||||
|   useEffect(() => { | ||||
|  | @ -162,7 +164,11 @@ export function PostComment(props: { | |||
|               Reply | ||||
|             </button> | ||||
|           )} | ||||
|           <Tipper comment={comment} tips={tips ?? {}} /> | ||||
|           <Tipper | ||||
|             comment={comment} | ||||
|             myTip={me ? tips[me.id] ?? 0 : 0} | ||||
|             totalTip={sum(Object.values(tips))} | ||||
|           /> | ||||
|         </Row> | ||||
|       </div> | ||||
|     </Row> | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue
	
	Block a user