From 472119028c566d147a8f74107afafbdda9fe1048 Mon Sep 17 00:00:00 2001 From: Jon Kafton <939376+jonkafton@users.noreply.github.com> Date: Wed, 6 Nov 2024 18:21:06 +0100 Subject: [PATCH 01/15] Warn on API calls during initial render not prefetched --- frontends/api/src/hooks/channels/index.ts | 5 ++- .../api/src/hooks/learningResources/index.ts | 15 ++++--- frontends/api/src/hooks/newsEvents/index.ts | 9 ++++- .../api/src/hooks/programLetters/index.ts | 2 +- .../api/src/hooks/searchSubscription/index.ts | 2 +- frontends/api/src/hooks/testimonials/index.ts | 9 ++++- frontends/api/src/hooks/widget_lists/index.ts | 6 ++- frontends/api/src/useQueryCacheWarning.tsx | 40 +++++++++++++++++++ .../DepartmentListingPage.tsx | 4 +- .../main/src/app-pages/HomePage/HomePage.tsx | 26 +++++------- .../app-pages/HomePage/NewsEventsSection.tsx | 2 +- frontends/main/src/app/departments/page.tsx | 23 +++++++++-- frontends/main/src/app/layout.tsx | 3 ++ frontends/main/src/app/page.tsx | 38 +++++++++++++++++- frontends/main/src/app/providers.tsx | 14 ++++++- 15 files changed, 157 insertions(+), 41 deletions(-) create mode 100644 frontends/api/src/useQueryCacheWarning.tsx diff --git a/frontends/api/src/hooks/channels/index.ts b/frontends/api/src/hooks/channels/index.ts index b86f798c58..dd8e9d8a35 100644 --- a/frontends/api/src/hooks/channels/index.ts +++ b/frontends/api/src/hooks/channels/index.ts @@ -1,10 +1,9 @@ import { UseQueryOptions, useMutation, - useQuery, useQueryClient, } from "@tanstack/react-query" - +import { useQuery } from "../../useQueryCacheWarning" import { channelsApi } from "../../clients" import type { ChannelsApiChannelsListRequest, @@ -27,6 +26,7 @@ const useChannelDetail = (channelType: string, channelName: string) => { ...channels.detailByType(channelType, channelName), }) } + const useChannelCounts = (channelType: string) => { return useQuery({ ...channels.countsByType(channelType), @@ -54,4 +54,5 @@ export { useChannelsList, useChannelPartialUpdate, useChannelCounts, + channels as channelsKeyFactory, } diff --git a/frontends/api/src/hooks/learningResources/index.ts b/frontends/api/src/hooks/learningResources/index.ts index 7633c806f8..6ce269c7cc 100644 --- a/frontends/api/src/hooks/learningResources/index.ts +++ b/frontends/api/src/hooks/learningResources/index.ts @@ -2,9 +2,9 @@ import { UseQueryOptions, useInfiniteQuery, useMutation, - useQuery, useQueryClient, } from "@tanstack/react-query" +import { useQuery } from "../../useQueryCacheWarning" import { learningpathsApi, learningResourcesApi, @@ -500,13 +500,6 @@ const useSchoolsList = () => { return useQuery(learningResources.schools()) } -/* - * Not intended to be imported except for special cases. - * It's used in the ResourceCarousel to dynamically build a single useQueries hook - * from config because a React component cannot conditionally call hooks during renders. - */ -export { default as learningResourcesKeyFactory } from "./keyFactory" - export { useLearningResourcesList, useFeaturedLearningResourcesList, @@ -538,4 +531,10 @@ export { useListItemMove, usePlatformsList, useSchoolsList, + /* + * Not intended to be imported except for special cases. + * It's used in the ResourceCarousel to dynamically build a single useQueries hook + * from config because a React component cannot conditionally call hooks during renders. + */ + learningResources as learningResourcesKeyFactory, } diff --git a/frontends/api/src/hooks/newsEvents/index.ts b/frontends/api/src/hooks/newsEvents/index.ts index 0e14708dba..f725b2faba 100644 --- a/frontends/api/src/hooks/newsEvents/index.ts +++ b/frontends/api/src/hooks/newsEvents/index.ts @@ -1,4 +1,4 @@ -import { useQuery } from "@tanstack/react-query" +import { useQuery } from "../../useQueryCacheWarning" import newsEvents from "./keyFactory" import { NewsEventsApiNewsEventsListRequest, @@ -15,4 +15,9 @@ const useNewsEventsDetail = (id: number) => { return useQuery(newsEvents.detail(id)) } -export { useNewsEventsList, useNewsEventsDetail, NewsEventsListFeedTypeEnum } +export { + useNewsEventsList, + useNewsEventsDetail, + NewsEventsListFeedTypeEnum, + newsEvents as newsEventsKeyFactory, +} diff --git a/frontends/api/src/hooks/programLetters/index.ts b/frontends/api/src/hooks/programLetters/index.ts index a9de8224cc..98cb44cf56 100644 --- a/frontends/api/src/hooks/programLetters/index.ts +++ b/frontends/api/src/hooks/programLetters/index.ts @@ -1,4 +1,4 @@ -import { useQuery } from "@tanstack/react-query" +import { useQuery } from "../../useQueryCacheWarning" import programLetters from "./keyFactory" /** diff --git a/frontends/api/src/hooks/searchSubscription/index.ts b/frontends/api/src/hooks/searchSubscription/index.ts index 6119759b9d..d49d114155 100644 --- a/frontends/api/src/hooks/searchSubscription/index.ts +++ b/frontends/api/src/hooks/searchSubscription/index.ts @@ -2,8 +2,8 @@ import { useMutation, UseQueryOptions, useQueryClient, - useQuery, } from "@tanstack/react-query" +import { useQuery } from "../../useQueryCacheWarning" import searchSubscriptions from "./keyFactory" import type { LearningResourcesUserSubscriptionApiLearningResourcesUserSubscriptionSubscribeCreateRequest as subscriptionCreateRequest } from "../../generated/v1" import { searchSubscriptionApi } from "../../clients" diff --git a/frontends/api/src/hooks/testimonials/index.ts b/frontends/api/src/hooks/testimonials/index.ts index 2ee1c6f4c5..ac96064561 100644 --- a/frontends/api/src/hooks/testimonials/index.ts +++ b/frontends/api/src/hooks/testimonials/index.ts @@ -1,4 +1,5 @@ -import { UseQueryOptions, useQuery } from "@tanstack/react-query" +import { UseQueryOptions } from "@tanstack/react-query" +import { useQuery } from "../../useQueryCacheWarning" import type { TestimonialsApiTestimonialsListRequest } from "../../generated/v0" import testimonials from "./keyFactory" @@ -23,4 +24,8 @@ const useTestimonialDetail = (id: number | undefined) => { }) } -export { useTestimonialDetail, useTestimonialList } +export { + useTestimonialDetail, + useTestimonialList, + testimonials as testimonialsKeyFactory, +} diff --git a/frontends/api/src/hooks/widget_lists/index.ts b/frontends/api/src/hooks/widget_lists/index.ts index 2ac9aff1f4..30f8213138 100644 --- a/frontends/api/src/hooks/widget_lists/index.ts +++ b/frontends/api/src/hooks/widget_lists/index.ts @@ -1,10 +1,12 @@ -import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query" +import { useMutation, useQueryClient } from "@tanstack/react-query" +import { useQuery } from "../../useQueryCacheWarning" import { widgetListsApi } from "../../clients" import widgetLists from "./keyFactory" import { WidgetInstance } from "api/v0" + /** - * Query is diabled if id is undefined. + * Query is disabled if id is undefined. */ const useWidgetList = (id: number | undefined) => { return useQuery({ diff --git a/frontends/api/src/useQueryCacheWarning.tsx b/frontends/api/src/useQueryCacheWarning.tsx new file mode 100644 index 0000000000..2afa89f1e2 --- /dev/null +++ b/frontends/api/src/useQueryCacheWarning.tsx @@ -0,0 +1,40 @@ +import { + useQuery as useQueryOriginal, + UseQueryOptions, + UseQueryResult, + QueryKey, + useQueryClient, +} from "@tanstack/react-query" + +/* Wraps useQuery to check that the query cache is populated for a given query key. + * Queries are expected to have been prefetched on the server. A warning is logged + * if the query cache is empty for the key before the initial render has completed. + */ +export const useQuery = < + TQueryFnData = unknown, + TError = unknown, + TData = TQueryFnData, + TQueryKey extends QueryKey = QueryKey, +>( + options: Omit< + UseQueryOptions, + "initialData" + > & { + initialData?: () => undefined + }, +): UseQueryResult => { + const queryClient = useQueryClient() + + const cached = queryClient.getQueryState(options.queryKey!) + + const initialLoaded = queryClient.getQueryData(["initialRenderComplete"]) + + if (!cached && !initialLoaded) { + console.warn( + options.queryKey, + "QueryCache was empty - content was not prefetched on the server for query key", + ) + } + + return useQueryOriginal(options) +} diff --git a/frontends/main/src/app-pages/DepartmentListingPage/DepartmentListingPage.tsx b/frontends/main/src/app-pages/DepartmentListingPage/DepartmentListingPage.tsx index 8399d741db..2ed809d709 100644 --- a/frontends/main/src/app-pages/DepartmentListingPage/DepartmentListingPage.tsx +++ b/frontends/main/src/app-pages/DepartmentListingPage/DepartmentListingPage.tsx @@ -16,6 +16,7 @@ import { import { pluralize, backgroundSrcSetCSS } from "ol-utilities" import type { LearningResourceSchool } from "api" import { useSchoolsList } from "api/hooks/learningResources" +import { useChannelCounts } from "api/hooks/channels" import { RiPaletteLine, RiArrowRightSLine, @@ -29,7 +30,6 @@ import { import { HOME } from "@/common/urls" import backgroundSteps from "@/public/images/backgrounds/background_steps.jpg" import { aggregateProgramCounts, aggregateCourseCounts } from "@/common/utils" -import { useChannelCounts } from "api/hooks/channels" const SCHOOL_ICONS: Record = { // School of Architecture and Planning @@ -235,3 +235,5 @@ const DepartmentListingPage: React.FC = () => { } export default DepartmentListingPage + +export const test1 = [1, 2] diff --git a/frontends/main/src/app-pages/HomePage/HomePage.tsx b/frontends/main/src/app-pages/HomePage/HomePage.tsx index 475e53efd7..ac4e7891a0 100644 --- a/frontends/main/src/app-pages/HomePage/HomePage.tsx +++ b/frontends/main/src/app-pages/HomePage/HomePage.tsx @@ -1,6 +1,6 @@ "use client" -import React, { Suspense } from "react" +import React from "react" import { Container, styled, theme } from "ol-components" import HeroSearch from "@/page-components/HeroSearch/HeroSearch" import BrowseTopicsSection from "./BrowseTopicsSection" @@ -52,25 +52,21 @@ const HomePage: React.FC = () => {
- - - +
- - - + diff --git a/frontends/main/src/app-pages/HomePage/NewsEventsSection.tsx b/frontends/main/src/app-pages/HomePage/NewsEventsSection.tsx index bcf7d7227f..0a95482b94 100644 --- a/frontends/main/src/app-pages/HomePage/NewsEventsSection.tsx +++ b/frontends/main/src/app-pages/HomePage/NewsEventsSection.tsx @@ -286,7 +286,7 @@ const NewsEventsSection: React.FC = () => { Stories - + {stories.map((item, index) => ( { + const queryClient = new QueryClient() + + await Promise.all([ + queryClient.prefetchQuery(channelsKeyFactory.countsByType("department")), + queryClient.prefetchQuery(learningResourcesKeyFactory.schools()), + ]) + + const dehydratedState = dehydrate(queryClient) -const Page: React.FC = () => { - return + return ( + + + + ) } export default Page diff --git a/frontends/main/src/app/layout.tsx b/frontends/main/src/app/layout.tsx index a43a4e6ab5..eddde8ceff 100644 --- a/frontends/main/src/app/layout.tsx +++ b/frontends/main/src/app/layout.tsx @@ -5,6 +5,7 @@ import { PageWrapper, PageWrapperInner } from "./styled" import Providers from "./providers" import { MITLearnGlobalStyles } from "ol-components" import Script from "next/script" +// import { PrefetchProvider } from "./PrefetchProvider" import "./GlobalStyles" @@ -26,12 +27,14 @@ export default function RootLayout({ + {/* */}
{children}