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 { FreeResponseContract } from 'common/contract'
import { ContractComment } from 'common/comment' import { ContractComment } from 'common/comment'
import React, { useEffect, useRef, useState } from 'react' import React, { useEffect, useRef, useState } from 'react'
import { sum } from 'lodash'
import { Col } from 'web/components/layout/col' import { Col } from 'web/components/layout/col'
import { Row } from 'web/components/layout/row' import { Row } from 'web/components/layout/row'
import { Avatar } from 'web/components/avatar' import { Avatar } from 'web/components/avatar'
@ -14,6 +15,8 @@ import {
} from 'web/components/feed/feed-comments' } from 'web/components/feed/feed-comments'
import { CopyLinkDateTimeComponent } from 'web/components/feed/copy-link-date-time' import { CopyLinkDateTimeComponent } from 'web/components/feed/copy-link-date-time'
import { useRouter } from 'next/router' 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 { CommentTipMap } from 'web/hooks/use-tip-txns'
import { UserLink } from 'web/components/user-link' import { UserLink } from 'web/components/user-link'
@ -27,11 +30,17 @@ export function FeedAnswerCommentGroup(props: {
const { username, avatarUrl, name, text } = answer const { username, avatarUrl, name, text } = answer
const [replyTo, setReplyTo] = useState<ReplyTo>() const [replyTo, setReplyTo] = useState<ReplyTo>()
const user = useUser()
const router = useRouter() const router = useRouter()
const answerElementId = `answer-${answer.id}` const answerElementId = `answer-${answer.id}`
const highlighted = router.asPath.endsWith(`#${answerElementId}`) const highlighted = router.asPath.endsWith(`#${answerElementId}`)
const answerRef = useRef<HTMLDivElement>(null) const answerRef = useRef<HTMLDivElement>(null)
const onSubmitComment = useEvent(() => setReplyTo(undefined))
const onReplyClick = useEvent((comment: ContractComment) => {
setReplyTo({ id: comment.id, username: comment.userUsername })
})
useEffect(() => { useEffect(() => {
if (highlighted && answerRef.current != null) { if (highlighted && answerRef.current != null) {
answerRef.current.scrollIntoView(true) answerRef.current.scrollIntoView(true)
@ -95,10 +104,10 @@ export function FeedAnswerCommentGroup(props: {
indent={true} indent={true}
contract={contract} contract={contract}
comment={comment} comment={comment}
tips={tips[comment.id] ?? {}} myTip={user ? tips[comment.id]?.[user.id] : undefined}
onReplyClick={() => totalTip={sum(Object.values(tips[comment.id] ?? {}))}
setReplyTo({ id: comment.id, username: comment.userUsername }) showTip={true}
} onReplyClick={onReplyClick}
/> />
))} ))}
</Col> </Col>
@ -112,7 +121,7 @@ export function FeedAnswerCommentGroup(props: {
contract={contract} contract={contract}
parentAnswerOutcome={answer.number.toString()} parentAnswerOutcome={answer.number.toString()}
replyTo={replyTo} replyTo={replyTo}
onSubmitComment={() => setReplyTo(undefined)} onSubmitComment={onSubmitComment}
/> />
</div> </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 { ContractComment } from 'common/comment'
import { Contract } from 'common/contract' import { Contract } from 'common/contract'
import React, { useEffect, useRef, useState } from 'react'
import { useUser } from 'web/hooks/use-user' import { useUser } from 'web/hooks/use-user'
import { formatMoney } from 'common/util/format' import { formatMoney } from 'common/util/format'
import { useRouter } from 'next/router'
import { Row } from 'web/components/layout/row' import { Row } from 'web/components/layout/row'
import clsx from 'clsx'
import { Avatar } from 'web/components/avatar' import { Avatar } from 'web/components/avatar'
import { OutcomeLabel } from 'web/components/outcome-label' import { OutcomeLabel } from 'web/components/outcome-label'
import { CopyLinkDateTimeComponent } from 'web/components/feed/copy-link-date-time' 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 { Col } from 'web/components/layout/col'
import { track } from 'web/lib/service/analytics' import { track } from 'web/lib/service/analytics'
import { Tipper } from '../tipper' 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 { Content } from '../editor'
import { Editor } from '@tiptap/react'
import { UserLink } from 'web/components/user-link' import { UserLink } from 'web/components/user-link'
import { CommentInput } from '../comment-input' import { CommentInput } from '../comment-input'
import { AwardBountyButton } from 'web/components/award-bounty-button' import { AwardBountyButton } from 'web/components/award-bounty-button'
@ -32,6 +35,12 @@ export function FeedCommentThread(props: {
const { contract, threadComments, tips, parentComment } = props const { contract, threadComments, tips, parentComment } = props
const [replyTo, setReplyTo] = useState<ReplyTo>() 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 ( return (
<Col className="relative w-full items-stretch gap-3 pb-4"> <Col className="relative w-full items-stretch gap-3 pb-4">
<span <span
@ -44,10 +53,10 @@ export function FeedCommentThread(props: {
indent={commentIdx != 0} indent={commentIdx != 0}
contract={contract} contract={contract}
comment={comment} comment={comment}
tips={tips[comment.id] ?? {}} myTip={user ? tips[comment.id]?.[user.id] : undefined}
onReplyClick={() => totalTip={sum(Object.values(tips[comment.id] ?? {}))}
setReplyTo({ id: comment.id, username: comment.userUsername }) showTip={true}
} onReplyClick={onReplyClick}
/> />
))} ))}
{replyTo && ( {replyTo && (
@ -60,7 +69,7 @@ export function FeedCommentThread(props: {
contract={contract} contract={contract}
parentCommentId={parentComment.id} parentCommentId={parentComment.id}
replyTo={replyTo} replyTo={replyTo}
onSubmitComment={() => setReplyTo(undefined)} onSubmitComment={onSubmitComment}
/> />
</Col> </Col>
)} )}
@ -68,14 +77,17 @@ export function FeedCommentThread(props: {
) )
} }
export function FeedComment(props: { export const FeedComment = memo(function FeedComment(props: {
contract: Contract contract: Contract
comment: ContractComment comment: ContractComment
tips?: CommentTips showTip?: boolean
myTip?: number
totalTip?: number
indent?: boolean 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 { const {
text, text,
content, content,
@ -180,12 +192,18 @@ export function FeedComment(props: {
{onReplyClick && ( {onReplyClick && (
<button <button
className="font-bold hover:underline" className="font-bold hover:underline"
onClick={onReplyClick} onClick={() => onReplyClick(comment)}
> >
Reply Reply
</button> </button>
)} )}
{tips && <Tipper comment={comment} tips={tips} />} {showTip && (
<Tipper
comment={comment}
myTip={myTip ?? 0}
totalTip={totalTip ?? 0}
/>
)}
{(contract.openCommentBounties ?? 0) > 0 && ( {(contract.openCommentBounties ?? 0) > 0 && (
<AwardBountyButton comment={comment} contract={contract} /> <AwardBountyButton comment={comment} contract={contract} />
)} )}
@ -193,7 +211,7 @@ export function FeedComment(props: {
</div> </div>
</Row> </Row>
) )
} })
function CommentStatus(props: { function CommentStatus(props: {
contract: Contract contract: Contract

View File

@ -1,10 +1,9 @@
import { useEffect, useRef, useState } from 'react' import { useEffect, useRef, useState } from 'react'
import toast from 'react-hot-toast' import toast from 'react-hot-toast'
import { debounce, sum } from 'lodash' import { debounce } from 'lodash'
import { Comment } from 'common/comment' import { Comment } from 'common/comment'
import { User } from 'common/user' import { User } from 'common/user'
import { CommentTips } from 'web/hooks/use-tip-txns'
import { useUser } from 'web/hooks/use-user' import { useUser } from 'web/hooks/use-user'
import { transact } from 'web/lib/firebase/api' import { transact } from 'web/lib/firebase/api'
import { track } from 'web/lib/service/analytics' import { track } from 'web/lib/service/analytics'
@ -13,25 +12,27 @@ import { Row } from './layout/row'
import { LIKE_TIP_AMOUNT } from 'common/like' import { LIKE_TIP_AMOUNT } from 'common/like'
import { formatMoney } from 'common/util/format' import { formatMoney } from 'common/util/format'
export function Tipper(prop: { comment: Comment; tips: CommentTips }) { export function Tipper(prop: {
const { comment, tips } = prop comment: Comment
myTip: number
totalTip: number
}) {
const { comment, myTip, totalTip } = prop
const me = useUser() 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 // listen for user being set
const initialized = useRef(false) const initialized = useRef(false)
useEffect(() => { useEffect(() => {
if (tips[myId] && !initialized.current) { if (myTip && !initialized.current) {
setLocalTip(tips[myId]) setLocalTip(myTip)
initialized.current = true 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 // declare debounced function only on first render
const [saveTip] = useState(() => const [saveTip] = useState(() =>
@ -73,7 +74,7 @@ export function Tipper(prop: { comment: Comment; tips: CommentTips }) {
const addTip = (delta: number) => { const addTip = (delta: number) => {
setLocalTip(localTip + delta) 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)}!`) toast(`You tipped ${comment.userName} ${formatMoney(LIKE_TIP_AMOUNT)}!`)
} }

View File

@ -1,5 +1,6 @@
import { track } from '@amplitude/analytics-browser' import { track } from '@amplitude/analytics-browser'
import { Editor } from '@tiptap/core' import { Editor } from '@tiptap/core'
import { sum } from 'lodash'
import clsx from 'clsx' import clsx from 'clsx'
import { PostComment } from 'common/comment' import { PostComment } from 'common/comment'
import { Post } from 'common/post' import { Post } from 'common/post'
@ -109,6 +110,7 @@ export function PostComment(props: {
const { text, content, userUsername, userName, userAvatarUrl, createdTime } = const { text, content, userUsername, userName, userAvatarUrl, createdTime } =
comment comment
const me = useUser()
const [highlighted, setHighlighted] = useState(false) const [highlighted, setHighlighted] = useState(false)
const router = useRouter() const router = useRouter()
useEffect(() => { useEffect(() => {
@ -162,7 +164,11 @@ export function PostComment(props: {
Reply Reply
</button> </button>
)} )}
<Tipper comment={comment} tips={tips ?? {}} /> <Tipper
comment={comment}
myTip={me ? tips[me.id] ?? 0 : 0}
totalTip={sum(Object.values(tips))}
/>
</Row> </Row>
</div> </div>
</Row> </Row>