Skip to content

Commit 2173201

Browse files
jonkaftonmbertrand
authored andcommitted
Server rendering for homepage, units and topics listing pages (#1822)
* Warn on API calls during initial render not prefetched * Full prefetch for homepage (commented) * Prefetch utility * Check for queries prefetched that are not needed during render and warn * No need to stringify * Replace useQuery overrides with decoupled cache check (wip) * Observer count for unnecessary prefetch warnings * Remove useQuery override * Test prefetch warnings * Remove inadvertent/unnecessary diff * Remove comments * Prefetch homepage. Invalidate learning resource cache items * Remove comment * Update comment * Temp remove featured resource list shuffle * Remove unused * Prefetch calls * Prefetch topics page * Rename key factory re-exports * Units page prefetch * Single request for all unit channel details * Update unit listing page tests * Page arg types * Optional route params * Remove comment * Remove unused * Remove comment
1 parent 43056fb commit 2173201

File tree

29 files changed

+280
-173
lines changed

29 files changed

+280
-173
lines changed

frontends/api/src/hooks/channels/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,5 +55,5 @@ export {
5555
useChannelsList,
5656
useChannelPartialUpdate,
5757
useChannelCounts,
58-
channels as channelsKeyFactory,
58+
channels,
5959
}

frontends/api/src/hooks/learningResources/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -531,5 +531,5 @@ export {
531531
useListItemMove,
532532
usePlatformsList,
533533
useSchoolsList,
534-
learningResources as learningResourcesKeyFactory,
534+
learningResources,
535535
}

frontends/api/src/hooks/learningResources/keyFactory.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,11 @@ import type {
3333

3434
import { createQueryKeys } from "@lukemorales/query-key-factory"
3535

36+
/* TODO we can add this back in once resource user list membership is implemented in the front end
37+
* Currently we fetch on the server for public resources and then refetch on the client for the
38+
* user lists parents. Randomizing therefore causes a content flicker as the client loaded
39+
* sequence replaces the server rendered sequence.
40+
3641
const shuffle = ([...arr]) => {
3742
let m = arr.length
3843
while (m) {
@@ -61,6 +66,7 @@ const randomizeResults = ([...results]) => {
6166
})
6267
return randomizedResults
6368
}
69+
*/
6470

6571
const learningResources = createQueryKeys("learningResources", {
6672
detail: (id: number) => ({
@@ -81,7 +87,7 @@ const learningResources = createQueryKeys("learningResources", {
8187
queryKey: [params],
8288
queryFn: () => {
8389
return featuredApi.featuredList(params).then((res) => {
84-
res.data.results = randomizeResults(res.data?.results)
90+
// res.data.results = randomizeResults(res.data?.results)
8591
return res.data
8692
})
8793
},

frontends/api/src/hooks/newsEvents/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,5 +19,5 @@ export {
1919
useNewsEventsList,
2020
useNewsEventsDetail,
2121
NewsEventsListFeedTypeEnum,
22-
newsEvents as newsEventsKeyFactory,
22+
newsEvents,
2323
}

frontends/api/src/hooks/testimonials/index.ts

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,4 @@ const useTestimonialDetail = (id: number | undefined) => {
2323
})
2424
}
2525

26-
export {
27-
useTestimonialDetail,
28-
useTestimonialList,
29-
testimonials as testimonialsKeyFactory,
30-
}
26+
export { useTestimonialDetail, useTestimonialList, testimonials }

frontends/api/src/ssr/prefetch.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,19 @@
11
import { QueryClient, dehydrate } from "@tanstack/react-query"
22
import type { Query } from "@tanstack/react-query"
33

4-
// Utility to avoid repetition in server components
5-
export const prefetch = async (queries: (Query | unknown)[]) => {
6-
const queryClient = new QueryClient()
4+
/* Utility to avoid repetition in server components
5+
* Optionally pass the queryClient returned from a previous prefetch
6+
* where queries are dependent on previous results
7+
*/
8+
export const prefetch = async (
9+
queries: (Query | unknown)[],
10+
queryClient?: QueryClient,
11+
) => {
12+
queryClient = queryClient || new QueryClient()
713

814
await Promise.all(
915
queries.map((query) => queryClient.prefetchQuery(query as Query)),
1016
)
1117

12-
return dehydrate(queryClient)
18+
return { dehydratedState: dehydrate(queryClient), queryClient }
1319
}

frontends/api/src/ssr/usePrefetchWarnings.test.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { usePrefetchWarnings } from "./usePrefetchWarnings"
44
import { setupReactQueryTest } from "../hooks/test-utils"
55
import { urls, factories, setMockResponse } from "../test-utils"
66
import {
7-
learningResourcesKeyFactory,
7+
learningResources,
88
useLearningResourcesDetail,
99
} from "../hooks/learningResources"
1010

@@ -45,7 +45,7 @@ describe("SSR prefetch warnings", () => {
4545
expect.objectContaining({
4646
disabled: false,
4747
initialStatus: "loading",
48-
key: learningResourcesKeyFactory.detail(1).queryKey,
48+
key: learningResources.detail(1).queryKey,
4949
observerCount: 1,
5050
}),
5151
],
@@ -65,7 +65,7 @@ describe("SSR prefetch warnings", () => {
6565
wrapper,
6666
initialProps: {
6767
queryClient,
68-
exemptions: [learningResourcesKeyFactory.detail(1).queryKey],
68+
exemptions: [learningResources.detail(1).queryKey],
6969
},
7070
})
7171

@@ -83,7 +83,7 @@ describe("SSR prefetch warnings", () => {
8383
const { unmount } = renderHook(
8484
() =>
8585
useQuery({
86-
...learningResourcesKeyFactory.detail(1),
86+
...learningResources.detail(1),
8787
initialData: data,
8888
}),
8989
{ wrapper },
@@ -105,9 +105,9 @@ describe("SSR prefetch warnings", () => {
105105
[
106106
{
107107
disabled: false,
108-
hash: JSON.stringify(learningResourcesKeyFactory.detail(1).queryKey),
108+
hash: JSON.stringify(learningResources.detail(1).queryKey),
109109
initialStatus: "success",
110-
key: learningResourcesKeyFactory.detail(1).queryKey,
110+
key: learningResources.detail(1).queryKey,
111111
observerCount: 0,
112112
status: "success",
113113
},

frontends/api/src/ssr/usePrefetchWarnings.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,13 @@ export const usePrefetchWarnings = ({
4545
*/
4646
useEffect(
4747
() => {
48+
/* Invalidate all learning resource queries so they are re-fetched in the client
49+
* for session specific data (user list memberships).
50+
* This is interim, until the memberships endpoint is implemented in the frontend
51+
* TODO remove once complete (see https://github.com/mitodl/hq/issues/5159)
52+
*/
53+
queryClient.invalidateQueries(["learningResources"])
54+
4855
if (process.env.NODE_ENV === "production") {
4956
return
5057
}

frontends/api/src/test-utils/factories/channels.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,7 @@ const _channelShared = (): Partial<Omit<Channel, "channel_type">> => {
148148
key: faker.lorem.slug(),
149149
value: faker.lorem.slug(),
150150
}),
151+
channel_url: `${faker.internet.url({ appendSlash: false })}${faker.system.directoryPath()}`,
151152
}
152153
}
153154

frontends/api/src/test-utils/urls.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import type {
99
NewsEventsApiNewsEventsListRequest,
1010
TestimonialsApi,
11+
ChannelsApi,
1112
} from "../generated/v0"
1213
import type {
1314
LearningResourcesApi as LRApi,
@@ -179,6 +180,8 @@ const channels = {
179180
details: (channelType: string, name: string) =>
180181
`${API_BASE_URL}/api/v0/channels/type/${channelType}/${name}/`,
181182
patch: (id: number) => `${API_BASE_URL}/api/v0/channels/${id}/`,
183+
list: (params?: Paramsv0<ChannelsApi, "channelsList">) =>
184+
`${API_BASE_URL}/api/v0/channels/${query(params)}`,
182185
}
183186

184187
const widgetLists = {

frontends/main/src/app-pages/UnitsListingPage/UnitCard.tsx

Lines changed: 13 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import React from "react"
2-
import { LearningResourceOfferorDetail, OfferedByEnum } from "api"
2+
import type { OfferedByEnum } from "api"
3+
import type { UnitChannel } from "api/v0"
34
import {
45
Card,
56
Skeleton,
@@ -8,7 +9,6 @@ import {
89
theme,
910
UnitLogo,
1011
} from "ol-components"
11-
import { useChannelDetail } from "api/hooks/channels"
1212
import Link from "next/link"
1313

1414
const CardStyled = styled(Card)({
@@ -102,25 +102,23 @@ const CountsText = styled(Typography)(({ theme }) => ({
102102
}))
103103

104104
interface UnitCardsProps {
105-
units: LearningResourceOfferorDetail[] | undefined
105+
channels: UnitChannel[] | undefined
106106
courseCounts: Record<string, number>
107107
programCounts: Record<string, number>
108108
}
109109

110110
interface UnitCardProps {
111-
unit: LearningResourceOfferorDetail
111+
channel: UnitChannel
112112
courseCount: number
113113
programCount: number
114114
}
115115

116116
const UnitCard: React.FC<UnitCardProps> = (props) => {
117-
const { unit, courseCount, programCount } = props
118-
const channelDetailQuery = useChannelDetail("unit", unit.code)
119-
const channelDetail = channelDetailQuery.data
120-
const unitUrl = channelDetail?.channel_url
117+
const { channel, courseCount, programCount } = props
118+
const unit = channel.unit_detail.unit
121119

122-
if (!unitUrl) return null
123-
const href = unitUrl && new URL(unitUrl).pathname
120+
if (!channel.channel_url) return null
121+
const href = new URL(channel.channel_url).pathname
124122

125123
return (
126124
<CardStyled forwardClicksToLink data-testid={`unit-card-${unit.code}`}>
@@ -134,9 +132,7 @@ const UnitCard: React.FC<UnitCardProps> = (props) => {
134132
</LogoContainer>
135133
<CardBottom>
136134
<ValuePropContainer>
137-
<HeadingText>
138-
{channelDetail?.configuration?.heading}
139-
</HeadingText>
135+
<HeadingText>{channel?.configuration?.heading}</HeadingText>
140136
</ValuePropContainer>
141137
<CountsTextContainer>
142138
<CountsText data-testid={`course-count-${unit.code}`}>
@@ -174,17 +170,18 @@ export const UnitCardLoading = () => {
174170
}
175171

176172
export const UnitCards: React.FC<UnitCardsProps> = (props) => {
177-
const { units, courseCounts, programCounts } = props
173+
const { channels, courseCounts, programCounts } = props
178174
return (
179175
<>
180-
{units?.map((unit) => {
176+
{channels?.map((channel) => {
177+
const unit = channel.unit_detail.unit
181178
const courseCount = courseCounts[unit.code] || 0
182179
const programCount = programCounts[unit.code] || 0
183180

184181
return unit.value_prop ? (
185182
<UnitCard
186183
key={unit.code}
187-
unit={unit}
184+
channel={channel}
188185
courseCount={courseCount}
189186
programCount={programCount}
190187
/>

0 commit comments

Comments
 (0)