Fix some hydration issues (#1033)

* Extract `useIsClient` hook

* Fix a bunch of hydration bugs relevant to the contract page
This commit is contained in:
Marshall Polaris 2022-10-12 14:07:07 -07:00 committed by GitHub
parent aa717a767d
commit c44f223064
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 29 additions and 17 deletions

View File

@ -34,6 +34,7 @@ import { ExtraContractActionsRow } from './extra-contract-actions-row'
import { GroupLink } from 'common/group' import { GroupLink } from 'common/group'
import { Subtitle } from '../subtitle' import { Subtitle } from '../subtitle'
import { useIsMobile } from 'web/hooks/use-is-mobile' import { useIsMobile } from 'web/hooks/use-is-mobile'
import { useIsClient } from 'web/hooks/use-is-client'
import { import {
BountiedContractBadge, BountiedContractBadge,
BountiedContractSmallBadge, BountiedContractSmallBadge,
@ -52,22 +53,23 @@ export function MiscDetails(props: {
const { volume, closeTime, isResolved, createdTime, resolutionTime } = const { volume, closeTime, isResolved, createdTime, resolutionTime } =
contract contract
const isClient = useIsClient()
const isNew = createdTime > Date.now() - DAY_MS && !isResolved const isNew = createdTime > Date.now() - DAY_MS && !isResolved
const groupToDisplay = getGroupLinkToDisplay(contract) const groupToDisplay = getGroupLinkToDisplay(contract)
return ( return (
<Row className="items-center gap-3 truncate text-sm text-gray-400"> <Row className="items-center gap-3 truncate text-sm text-gray-400">
{showTime === 'close-date' ? ( {isClient && showTime === 'close-date' ? (
<Row className="gap-0.5 whitespace-nowrap"> <Row className="gap-0.5 whitespace-nowrap">
<ClockIcon className="h-5 w-5" /> <ClockIcon className="h-5 w-5" />
{(closeTime || 0) < Date.now() ? 'Closed' : 'Closes'}{' '} {(closeTime || 0) < Date.now() ? 'Closed' : 'Closes'}{' '}
{fromNow(closeTime || 0)} {fromNow(closeTime || 0)}
</Row> </Row>
) : showTime === 'resolve-date' && resolutionTime !== undefined ? ( ) : isClient && showTime === 'resolve-date' && resolutionTime ? (
<Row className="gap-0.5"> <Row className="gap-0.5">
<ClockIcon className="h-5 w-5" /> <ClockIcon className="h-5 w-5" />
{'Resolved '} {'Resolved '}
{fromNow(resolutionTime || 0)} {fromNow(resolutionTime)}
</Row> </Row>
) : (contract?.featuredOnHomeRank ?? 0) > 0 ? ( ) : (contract?.featuredOnHomeRank ?? 0) > 0 ? (
<FeaturedContractBadge /> <FeaturedContractBadge />
@ -390,6 +392,7 @@ function EditableCloseDate(props: {
}) { }) {
const { closeTime, contract, isCreator, disabled } = props const { closeTime, contract, isCreator, disabled } = props
const isClient = useIsClient()
const dayJsCloseTime = dayjs(closeTime) const dayJsCloseTime = dayjs(closeTime)
const dayJsNow = dayjs() const dayJsNow = dayjs()
@ -452,7 +455,7 @@ function EditableCloseDate(props: {
className="w-full shrink-0 sm:w-fit" className="w-full shrink-0 sm:w-fit"
onClick={(e) => e.stopPropagation()} onClick={(e) => e.stopPropagation()}
onChange={(e) => setCloseDate(e.target.value)} onChange={(e) => setCloseDate(e.target.value)}
min={Date.now()} min={isClient ? Date.now() : undefined}
value={closeDate} value={closeDate}
/> />
<Input <Input
@ -479,14 +482,18 @@ function EditableCloseDate(props: {
</Col> </Col>
</Modal> </Modal>
<DateTimeTooltip <DateTimeTooltip
text={closeTime > Date.now() ? 'Trading ends:' : 'Trading ended:'} text={
isClient && closeTime <= Date.now()
? 'Trading ended:'
: 'Trading ends:'
}
time={closeTime} time={closeTime}
> >
<Row <Row
className={clsx(!disabled && isCreator ? 'cursor-pointer' : '')} className={clsx(!disabled && isCreator ? 'cursor-pointer' : '')}
onClick={() => !disabled && isCreator && setIsEditingCloseTime(true)} onClick={() => !disabled && isCreator && setIsEditingCloseTime(true)}
> >
{isSameDay ? ( {isSameDay && isClient ? (
<span className={'capitalize'}> {fromNow(closeTime)}</span> <span className={'capitalize'}> {fromNow(closeTime)}</span>
) : isSameYear ? ( ) : isSameYear ? (
dayJsCloseTime.format('MMM D') dayJsCloseTime.format('MMM D')

View File

@ -6,17 +6,19 @@ import { contractPath, getBinaryProbPercent } from 'web/lib/firebase/contracts'
import { fromNow } from 'web/lib/util/time' import { fromNow } from 'web/lib/util/time'
import { BinaryContractOutcomeLabel } from '../outcome-label' import { BinaryContractOutcomeLabel } from '../outcome-label'
import { getColor } from './quick-bet' import { getColor } from './quick-bet'
import { useIsClient } from 'web/hooks/use-is-client'
export function ContractMention(props: { contract: Contract }) { export function ContractMention(props: { contract: Contract }) {
const { contract } = props const { contract } = props
const { outcomeType, resolution } = contract const { outcomeType, resolution } = contract
const probTextColor = `text-${getColor(contract)}` const probTextColor = `text-${getColor(contract)}`
const isClient = useIsClient()
return ( return (
<Link href={contractPath(contract)}> <Link href={contractPath(contract)}>
<a <a
className="group inline whitespace-nowrap rounded-sm hover:bg-indigo-50 focus:bg-indigo-50" className="group inline whitespace-nowrap rounded-sm hover:bg-indigo-50 focus:bg-indigo-50"
title={tooltipLabel(contract)} title={isClient ? tooltipLabel(contract) : undefined}
> >
<span className="break-anywhere mr-0.5 whitespace-normal font-normal text-indigo-700"> <span className="break-anywhere mr-0.5 whitespace-normal font-normal text-indigo-700">
{contract.question} {contract.question}

View File

@ -5,6 +5,7 @@ import Link from 'next/link'
import { fromNow } from 'web/lib/util/time' import { fromNow } from 'web/lib/util/time'
import { ToastClipboard } from 'web/components/toast-clipboard' import { ToastClipboard } from 'web/components/toast-clipboard'
import { LinkIcon } from '@heroicons/react/outline' import { LinkIcon } from '@heroicons/react/outline'
import { useIsClient } from 'web/hooks/use-is-client'
export function CopyLinkDateTimeComponent(props: { export function CopyLinkDateTimeComponent(props: {
prefix: string prefix: string
@ -14,6 +15,7 @@ export function CopyLinkDateTimeComponent(props: {
className?: string className?: string
}) { }) {
const { prefix, slug, elementId, createdTime, className } = props const { prefix, slug, elementId, createdTime, className } = props
const isClient = useIsClient()
const [showToast, setShowToast] = useState(false) const [showToast, setShowToast] = useState(false)
function copyLinkToComment( function copyLinkToComment(
@ -36,7 +38,7 @@ export function CopyLinkDateTimeComponent(props: {
'text-greyscale-4 hover:bg-greyscale-1.5 mx-1 whitespace-nowrap rounded-sm px-1 text-xs transition-colors' 'text-greyscale-4 hover:bg-greyscale-1.5 mx-1 whitespace-nowrap rounded-sm px-1 text-xs transition-colors'
} }
> >
{fromNow(createdTime)} {isClient && fromNow(createdTime)}
{showToast && <ToastClipboard />} {showToast && <ToastClipboard />}
<LinkIcon className="ml-1 mb-0.5 inline" height={13} /> <LinkIcon className="ml-1 mb-0.5 inline" height={13} />
</a> </a>

View File

@ -1,22 +1,16 @@
import { DateTimeTooltip } from './datetime-tooltip' import { DateTimeTooltip } from './datetime-tooltip'
import React, { useEffect, useState } from 'react'
import { fromNow } from 'web/lib/util/time' import { fromNow } from 'web/lib/util/time'
import { useIsClient } from 'web/hooks/use-is-client'
export function RelativeTimestamp(props: { time: number }) { export function RelativeTimestamp(props: { time: number }) {
const { time } = props const { time } = props
const [isClient, setIsClient] = useState(false) const isClient = useIsClient()
useEffect(() => {
// Only render on client to prevent difference from server.
setIsClient(true)
}, [])
return ( return (
<DateTimeTooltip <DateTimeTooltip
className="ml-1 whitespace-nowrap text-gray-400" className="ml-1 whitespace-nowrap text-gray-400"
time={time} time={time}
> >
{isClient ? fromNow(time) : ''} {isClient && fromNow(time)}
</DateTimeTooltip> </DateTimeTooltip>
) )
} }

View File

@ -0,0 +1,7 @@
import { useEffect, useState } from 'react'
export const useIsClient = () => {
const [isClient, setIsClient] = useState(false)
useEffect(() => setIsClient(true), [])
return isClient
}