From c44f223064971077bd21f1aacdb929bab9ad68c8 Mon Sep 17 00:00:00 2001 From: Marshall Polaris Date: Wed, 12 Oct 2022 14:07:07 -0700 Subject: [PATCH] Fix some hydration issues (#1033) * Extract `useIsClient` hook * Fix a bunch of hydration bugs relevant to the contract page --- web/components/contract/contract-details.tsx | 19 +++++++++++++------ web/components/contract/contract-mention.tsx | 4 +++- web/components/feed/copy-link-date-time.tsx | 4 +++- web/components/relative-timestamp.tsx | 12 +++--------- web/hooks/use-is-client.ts | 7 +++++++ 5 files changed, 29 insertions(+), 17 deletions(-) create mode 100644 web/hooks/use-is-client.ts diff --git a/web/components/contract/contract-details.tsx b/web/components/contract/contract-details.tsx index a277ae4d..1ed2c49d 100644 --- a/web/components/contract/contract-details.tsx +++ b/web/components/contract/contract-details.tsx @@ -34,6 +34,7 @@ import { ExtraContractActionsRow } from './extra-contract-actions-row' import { GroupLink } from 'common/group' import { Subtitle } from '../subtitle' import { useIsMobile } from 'web/hooks/use-is-mobile' +import { useIsClient } from 'web/hooks/use-is-client' import { BountiedContractBadge, BountiedContractSmallBadge, @@ -52,22 +53,23 @@ export function MiscDetails(props: { const { volume, closeTime, isResolved, createdTime, resolutionTime } = contract + const isClient = useIsClient() const isNew = createdTime > Date.now() - DAY_MS && !isResolved const groupToDisplay = getGroupLinkToDisplay(contract) return ( - {showTime === 'close-date' ? ( + {isClient && showTime === 'close-date' ? ( {(closeTime || 0) < Date.now() ? 'Closed' : 'Closes'}{' '} {fromNow(closeTime || 0)} - ) : showTime === 'resolve-date' && resolutionTime !== undefined ? ( + ) : isClient && showTime === 'resolve-date' && resolutionTime ? ( {'Resolved '} - {fromNow(resolutionTime || 0)} + {fromNow(resolutionTime)} ) : (contract?.featuredOnHomeRank ?? 0) > 0 ? ( @@ -390,6 +392,7 @@ function EditableCloseDate(props: { }) { const { closeTime, contract, isCreator, disabled } = props + const isClient = useIsClient() const dayJsCloseTime = dayjs(closeTime) const dayJsNow = dayjs() @@ -452,7 +455,7 @@ function EditableCloseDate(props: { className="w-full shrink-0 sm:w-fit" onClick={(e) => e.stopPropagation()} onChange={(e) => setCloseDate(e.target.value)} - min={Date.now()} + min={isClient ? Date.now() : undefined} value={closeDate} /> Date.now() ? 'Trading ends:' : 'Trading ended:'} + text={ + isClient && closeTime <= Date.now() + ? 'Trading ended:' + : 'Trading ends:' + } time={closeTime} > !disabled && isCreator && setIsEditingCloseTime(true)} > - {isSameDay ? ( + {isSameDay && isClient ? ( {fromNow(closeTime)} ) : isSameYear ? ( dayJsCloseTime.format('MMM D') diff --git a/web/components/contract/contract-mention.tsx b/web/components/contract/contract-mention.tsx index 09e47c79..a5fa8879 100644 --- a/web/components/contract/contract-mention.tsx +++ b/web/components/contract/contract-mention.tsx @@ -6,17 +6,19 @@ import { contractPath, getBinaryProbPercent } from 'web/lib/firebase/contracts' import { fromNow } from 'web/lib/util/time' import { BinaryContractOutcomeLabel } from '../outcome-label' import { getColor } from './quick-bet' +import { useIsClient } from 'web/hooks/use-is-client' export function ContractMention(props: { contract: Contract }) { const { contract } = props const { outcomeType, resolution } = contract const probTextColor = `text-${getColor(contract)}` + const isClient = useIsClient() return ( {contract.question} diff --git a/web/components/feed/copy-link-date-time.tsx b/web/components/feed/copy-link-date-time.tsx index 6b6b911a..9f2d3da4 100644 --- a/web/components/feed/copy-link-date-time.tsx +++ b/web/components/feed/copy-link-date-time.tsx @@ -5,6 +5,7 @@ import Link from 'next/link' import { fromNow } from 'web/lib/util/time' import { ToastClipboard } from 'web/components/toast-clipboard' import { LinkIcon } from '@heroicons/react/outline' +import { useIsClient } from 'web/hooks/use-is-client' export function CopyLinkDateTimeComponent(props: { prefix: string @@ -14,6 +15,7 @@ export function CopyLinkDateTimeComponent(props: { className?: string }) { const { prefix, slug, elementId, createdTime, className } = props + const isClient = useIsClient() const [showToast, setShowToast] = useState(false) 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' } > - {fromNow(createdTime)} + {isClient && fromNow(createdTime)} {showToast && } diff --git a/web/components/relative-timestamp.tsx b/web/components/relative-timestamp.tsx index eb03e324..6f343a73 100644 --- a/web/components/relative-timestamp.tsx +++ b/web/components/relative-timestamp.tsx @@ -1,22 +1,16 @@ import { DateTimeTooltip } from './datetime-tooltip' -import React, { useEffect, useState } from 'react' import { fromNow } from 'web/lib/util/time' +import { useIsClient } from 'web/hooks/use-is-client' export function RelativeTimestamp(props: { time: number }) { const { time } = props - const [isClient, setIsClient] = useState(false) - - useEffect(() => { - // Only render on client to prevent difference from server. - setIsClient(true) - }, []) - + const isClient = useIsClient() return ( - {isClient ? fromNow(time) : ''} + {isClient && fromNow(time)} ) } diff --git a/web/hooks/use-is-client.ts b/web/hooks/use-is-client.ts new file mode 100644 index 00000000..a192fd26 --- /dev/null +++ b/web/hooks/use-is-client.ts @@ -0,0 +1,7 @@ +import { useEffect, useState } from 'react' + +export const useIsClient = () => { + const [isClient, setIsClient] = useState(false) + useEffect(() => setIsClient(true), []) + return isClient +}