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:
Marshall Polaris 2022-10-04 23:16:56 -07:00 committed by GitHub
parent d2273087cf
commit ddb186dd98
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 69 additions and 35 deletions

View File

@ -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>
)}

View File

@ -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

View File

@ -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)}!`)
}

View File

@ -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>