Skip to content

Commit

Permalink
LocalDate and NoSSR components to render localized dates only on clie…
Browse files Browse the repository at this point in the history
…nt (#1831)

* LocalDate and NoSSR components to render localized dates only on client

* Remove unnecessary React.Fragment
  • Loading branch information
jonkafton authored Nov 18, 2024
1 parent ffa1098 commit 85fe937
Show file tree
Hide file tree
Showing 11 changed files with 72 additions and 22 deletions.
4 changes: 2 additions & 2 deletions frontends/main/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "PORT=${PORT:-8062} next dev",
"dev": "PORT=${PORT:-8062} TZ=UTC next dev",
"build": "next build",
"build:no-lint": "next build --no-lint",
"start": "next start",
"start": "TZ=UTC next start",
"lint": "next lint"
},
"dependencies": {
Expand Down
20 changes: 10 additions & 10 deletions frontends/main/src/app-pages/HomePage/NewsEventsSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {
NewsEventsListFeedTypeEnum,
} from "api/hooks/newsEvents"
import type { NewsFeedItem, EventFeedItem } from "api/v0"
import { formatDate } from "ol-utilities"
import { LocalDate } from "ol-utilities"
import { RiArrowRightSLine } from "@remixicon/react"
import Link from "next/link"

Expand Down Expand Up @@ -196,7 +196,7 @@ const Story: React.FC<{ item: NewsFeedItem; mobile: boolean }> = ({
{item.title}
</Card.Title>
<Card.Footer>
Published: {formatDate(item.news_details?.publish_date)}
Published: <LocalDate date={item.news_details?.publish_date} />
</Card.Footer>
</StoryCard>
)
Expand Down Expand Up @@ -226,16 +226,16 @@ const NewsEventsSection: React.FC = () => {
<Card.Content>
<EventDate>
<EventDay>
{formatDate(
(item as EventFeedItem).event_details?.event_datetime,
"D",
)}
<LocalDate
date={(item as EventFeedItem).event_details?.event_datetime}
format="D"
/>
</EventDay>
<EventMonth>
{formatDate(
(item as EventFeedItem).event_details?.event_datetime,
"MMM",
)}
<LocalDate
date={(item as EventFeedItem).event_details?.event_datetime}
format="MMM"
/>
</EventMonth>
</EventDate>
<Link href={item.url} data-card-link>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
} from "@remixicon/react"
import { LearningResource } from "api"
import {
formatDate,
LocalDate,
getReadableResourceType,
DEFAULT_RESOURCE_IMG,
getLearningResourcePrices,
Expand Down Expand Up @@ -149,7 +149,8 @@ const StartDate: React.FC<{ resource: LearningResource; size?: Size }> = ({
const format = size === "small" ? "MMM DD, YYYY" : "MMMM DD, YYYY"
const formatted = anytime
? "Anytime"
: startDate && formatDate(startDate, format)
: startDate && <LocalDate date={startDate} format={format} />

if (!formatted) return null

const showLabel = size !== "small" || anytime
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
} from "@remixicon/react"
import { ResourceTypeEnum, LearningResource } from "api"
import {
formatDate,
LocalDate,
getReadableResourceType,
DEFAULT_RESOURCE_IMG,
pluralize,
Expand Down Expand Up @@ -151,7 +151,7 @@ export const StartDate: React.FC<{ resource: LearningResource }> = ({
const startDate = getResourceDate(resource)
const formatted = anytime
? "Anytime"
: startDate && formatDate(startDate, "MMMM DD, YYYY")
: startDate && <LocalDate date={startDate} format="MMMM DD, YYYY" />
if (!formatted) return null

return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
getDisplayPrice,
getRunPrices,
showStartAnytime,
NoSSR,
} from "ol-utilities"

const DifferingRuns = styled.div({
Expand Down Expand Up @@ -103,7 +104,9 @@ const DifferingRunsTable: React.FC<{ resource: LearningResource }> = ({
</DifferingRunHeader>
{resource.runs?.map((run, index) => (
<DifferingRun key={index}>
<DateData>{formatRunDate(run, asTaughtIn)}</DateData>
<DateData>
<NoSSR>{formatRunDate(run, asTaughtIn)}</NoSSR>
</DateData>
{run.resource_prices && (
<PriceData>
<span>{getDisplayPrice(getRunPrices(run)["course"])}</span>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import {
formatRunDate,
getLearningResourcePrices,
showStartAnytime,
NoSSR,
} from "ol-utilities"
import { theme } from "../ThemeProvider/ThemeProvider"
import DifferingRunsTable from "./DifferingRunsTable"
Expand Down Expand Up @@ -255,7 +256,11 @@ const INFO_ITEMS: InfoItemConfig = [
const totalDatesWithRuns =
resource.runs?.filter((run) => run.start_date !== null).length || 0
if (allRunsAreIdentical(resource) && totalDatesWithRuns > 0) {
return <RunDates resource={resource} />
return (
<NoSSR>
<RunDates resource={resource} />
</NoSSR>
)
} else return null
},
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { ButtonLink } from "../Button/Button"
import type { LearningResource, LearningResourceRun } from "api"
import { ResourceTypeEnum, PlatformEnum } from "api"
import {
NoSSR,
formatDate,
capitalize,
DEFAULT_RESOURCE_IMG,
Expand Down Expand Up @@ -299,7 +300,7 @@ const ResourceDescription = ({ resource }: { resource?: LearningResource }) => {
return (
<Description
/**
* Resource descriptions can contain HTML. They are santiized on the
* Resource descriptions can contain HTML. They are sanitized on the
* backend during ETL. This is safe to render.
*/
dangerouslySetInnerHTML={{ __html: resource.description || "" }}
Expand Down Expand Up @@ -384,7 +385,7 @@ const LearningResourceExpandedV1: React.FC<LearningResourceExpandedV1Props> = ({
.map((run) => {
return {
value: run.id.toString(),
label: formatRunDate(run, asTaughtIn),
label: <NoSSR>{formatRunDate(run, asTaughtIn)}</NoSSR>,
}
}) ?? []

Expand Down Expand Up @@ -415,7 +416,7 @@ const LearningResourceExpandedV1: React.FC<LearningResourceExpandedV1Props> = ({
return (
<DateSingle>
<DateLabel>{label}</DateLabel>
{formatted ?? ""}
<NoSSR>{formatted ?? ""}</NoSSR>
</DateSingle>
)
}
Expand Down
20 changes: 20 additions & 0 deletions frontends/ol-utilities/src/date/LocalDate.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import React from "react"
import { NoSSR } from "../ssr/NoSSR"
import { formatDate } from "./format"

type LocalDateProps = {
date?: string | Date | null
/**
* A Moment.js format string. See https://momentjs.com/docs/#/displaying/format/
*/
format?: string
}

/* Component to render dates only on the client as these are displayed
* according to the user's locale (generally, not all Moment.js format tokens
* are localized) causing an error due to hydration mismatch.
*/
export const LocalDate = ({ date, format = "MMM D, YYYY" }: LocalDateProps) => {
if (!date) return null
return <NoSSR>{formatDate(date, format)}</NoSSR>
}
4 changes: 3 additions & 1 deletion frontends/ol-utilities/src/date/format.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import moment from "moment"

/* Instances must be wrapped in <NoSSR> to avoid SSR hydration mismatches.
*/
export const formatDate = (
/**
* Date string or date.
*/
date: string | Date,
/**
* A momentjs format string. See https://momentjs.com/docs/#/displaying/format/
* A Moment.js format string. See https://momentjs.com/docs/#/displaying/format/
*/
format = "MMM D, YYYY",
) => {
Expand Down
2 changes: 2 additions & 0 deletions frontends/ol-utilities/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
export * from "./styles"

export * from "./date/format"
export * from "./date/LocalDate"
export * from "./learning-resources/learning-resources"
export * from "./learning-resources/pricing"
export * from "./strings/html"
Expand All @@ -14,3 +15,4 @@ export * from "./hooks"
export * from "./querystrings"
export * from "./lib"
export * from "./images/backgroundImages"
export * from "./ssr/NoSSR"
16 changes: 16 additions & 0 deletions frontends/ol-utilities/src/ssr/NoSSR.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import React, { useState, useEffect, ReactNode } from "react"

type NoSSRProps = {
children: ReactNode
onSSR?: ReactNode
}

export const NoSSR: React.FC<NoSSRProps> = ({ children, onSSR = null }) => {
const [isClient, setClient] = useState(false)

useEffect(() => {
setClient(true)
}, [])

return isClient ? children : onSSR
}

0 comments on commit 85fe937

Please sign in to comment.