Fix some hydration issues (#1033)
* Extract `useIsClient` hook * Fix a bunch of hydration bugs relevant to the contract page
This commit is contained in:
parent
aa717a767d
commit
c44f223064
|
@ -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')
|
||||||
|
|
|
@ -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}
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
7
web/hooks/use-is-client.ts
Normal file
7
web/hooks/use-is-client.ts
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
import { useEffect, useState } from 'react'
|
||||||
|
|
||||||
|
export const useIsClient = () => {
|
||||||
|
const [isClient, setIsClient] = useState(false)
|
||||||
|
useEffect(() => setIsClient(true), [])
|
||||||
|
return isClient
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user