got some server hydration I think

This commit is contained in:
2024-11-08 15:16:53 -07:00
parent 011c28f0fd
commit 6fd5053ac5
20 changed files with 568 additions and 244 deletions

View File

@@ -1,5 +1,4 @@
import Modal, { useModal } from "@/components/Modal";
import { useLecturesByWeekQuery } from "@/hooks/localCourse/lectureHooks";
import { getLectureUrl } from "@/services/urlUtils";
import Link from "next/link";
import { useCourseContext } from "../../context/courseContext";
@@ -7,10 +6,11 @@ import NewItemForm from "../../modules/NewItemForm";
import { DraggableItem } from "../../context/drag/draggingContext";
import { useDragStyleContext } from "../../context/drag/dragStyleContext";
import { getLectureForDay } from "@/models/local/lectureUtils";
import { trpc } from "@/services/trpc/utils";
export function DayTitle({ day, dayAsDate }: { day: string; dayAsDate: Date }) {
const { courseName } = useCourseContext();
const { data: weeks } = useLecturesByWeekQuery();
const [weeks] = trpc.lectures.getLectures.useSuspenseQuery({ courseName });
const { setIsDragging } = useDragStyleContext();
const todaysLecture = getLectureForDay(weeks, dayAsDate);
const modal = useModal();

View File

@@ -1,9 +1,6 @@
"use client";
import { useUpdateAssignmentMutation } from "@/hooks/localCourse/assignmentHooks";
import {
useLecturesByWeekQuery,
useLectureUpdateMutation,
} from "@/hooks/localCourse/lectureHooks";
import { useLectureUpdateMutation } from "@/hooks/localCourse/lectureHooks";
import { useLocalCourseSettingsQuery } from "@/hooks/localCourse/localCoursesHooks";
import { useUpdatePageMutation } from "@/hooks/localCourse/pageHooks";
import { LocalAssignment } from "@/models/local/assignment/localAssignment";
@@ -20,6 +17,7 @@ import { Dispatch, SetStateAction, useCallback, DragEvent } from "react";
import { DraggableItem } from "./draggingContext";
import { getNewLockDate } from "./getNewLockDate";
import { useUpdateItemMutation } from "@/hooks/localCourse/courseItemHooks";
import { trpc } from "@/services/trpc/utils";
export function useItemDropOnDay({
setIsDragging,
@@ -35,7 +33,10 @@ export function useItemDropOnDay({
modal: { isOpen: boolean; openModal: () => void; closeModal: () => void };
}) {
const { data: settings } = useLocalCourseSettingsQuery();
const { data: weeks } = useLecturesByWeekQuery();
// const { data: weeks } = useLecturesByWeekQuery();
const [weeks] = trpc.lectures.getLectures.useSuspenseQuery({
courseName: settings.name,
});
const updateQuizMutation = useUpdateItemMutation("Quiz");
const updateLectureMutation = useLectureUpdateMutation();
const updateAssignmentMutation = useUpdateAssignmentMutation();

View File

@@ -1,5 +1,4 @@
import { fileStorageService } from "@/services/fileStorage/fileStorageService";
import CourseContextProvider from "./context/CourseContextProvider";
import { Suspense } from "react";
import { getQueryClient } from "@/app/providersQueryClientUtils";
import { dehydrate, HydrationBoundary } from "@tanstack/react-query";
@@ -8,69 +7,35 @@ import { createServerSideHelpers } from "@trpc/react-query/server";
import { trpcAppRouter } from "@/services/trpc/router/app";
import { createTrpcContext } from "@/services/trpc/context";
import superjson from "superjson";
import CourseContextProvider from "./context/CourseContextProvider";
export default async function CourseLayout({
children,
params: { courseName },
params,
}: {
children: React.ReactNode;
params: { courseName: string };
params: Promise<{ courseName: string }>;
}) {
const { courseName } = await params;
const decodedCourseName = decodeURIComponent(courseName);
if (courseName.includes(".js.map")) {
console.log("cannot load course that is .js.map " + decodedCourseName);
return <div></div>;
}
const settings = await fileStorageService.settings.getCourseSettings(
decodedCourseName
);
const queryClient = getQueryClient();
await hydrateCanvasCourse(settings.canvasId, queryClient);
const dehydratedState = dehydrate(queryClient);
const trpcHelper = createServerSideHelpers({
router: trpcAppRouter,
ctx: createTrpcContext(),
transformer: superjson,
});
// const settings = await fileStorageService.settings.getCourseSettings(
// decodedCourseName
// );
// const queryClient = getQueryClient();
// await hydrateCanvasCourse(settings.canvasId, queryClient);
// const dehydratedState = dehydrate(queryClient);
const allSettings = await fileStorageService.settings.getAllCoursesSettings();
await Promise.all(
allSettings.map(async (settings) => {
const courseName = settings.name;
const moduleNames = await fileStorageService.modules.getModuleNames(
courseName
);
await Promise.all(
moduleNames.map(async (moduleName) => {
await trpcHelper.assignment.getAllAssignments.prefetch({
courseName,
moduleName,
});
// await Promise.all(
// assignments.map(
// async (a) =>
// await trpcHelper.assignment.getAssignment.fetch({
// courseName,
// moduleName,
// assignmentName: a.name,
// })
// )
// );
})
);
})
);
const dehydratedTrpc = trpcHelper.dehydrate();
return (
<Suspense>
<HydrationBoundary state={dehydratedState}>
<HydrationBoundary state={dehydratedTrpc}>
<CourseContextProvider localCourseName={decodedCourseName}>
{children}
</CourseContextProvider>
</HydrationBoundary>
</HydrationBoundary>
{/* <HydrationBoundary state={dehydratedState}> */}
<CourseContextProvider localCourseName={decodedCourseName}>
{children}
</CourseContextProvider>
{/* </HydrationBoundary> */}
</Suspense>
);
}

View File

@@ -2,7 +2,6 @@
import { MonacoEditor } from "@/components/editor/MonacoEditor";
import {
useLecturesByWeekQuery,
useLectureUpdateMutation,
} from "@/hooks/localCourse/lectureHooks";
import {
@@ -13,9 +12,13 @@ import { useEffect, useState } from "react";
import LecturePreview from "./LecturePreview";
import EditLectureTitle from "./EditLectureTitle";
import LectureButtons from "./LectureButtons";
import { trpc } from "@/services/trpc/utils";
import { useCourseContext } from "../../context/courseContext";
export default function EditLecture({ lectureDay }: { lectureDay: string }) {
const { data: weeks } = useLecturesByWeekQuery();
// const { data: weeks } = useLecturesByWeekQuery();
const { courseName } = useCourseContext();
const [weeks] = trpc.lectures.getLectures.useSuspenseQuery({ courseName });
const updateLecture = useLectureUpdateMutation();
const lecture = weeks

View File

@@ -1,10 +1,10 @@
"use client";
import { useLecturesByWeekQuery } from "@/hooks/localCourse/lectureHooks";
import LecturePreview from "../LecturePreview";
import { getCourseUrl, getLectureUrl } from "@/services/urlUtils";
import { useCourseContext } from "../../../context/courseContext";
import Link from "next/link";
import { trpc } from "@/services/trpc/utils";
export default function LecturePreviewPage({
lectureDay,
@@ -12,7 +12,7 @@ export default function LecturePreviewPage({
lectureDay: string;
}) {
const { courseName } = useCourseContext();
const { data: weeks } = useLecturesByWeekQuery();
const [weeks] = trpc.lectures.getLectures.useSuspenseQuery({ courseName });
const lecture = weeks
.flatMap(({ lectures }) => lectures.map((lecture) => lecture))
.find((l) => l.date === lectureDay);

View File

@@ -31,15 +31,15 @@ export default function ExpandableModule({
}) {
const { itemDropOnModule } = useDraggingContext();
const [assignments] = useAssignmentsQuery(moduleName);
const { data: quizzes } = useItemsQueries(moduleName, "Quiz");
const { data: assignments } = useAssignmentsQuery(moduleName);
// const { data: quizzes } = useItemsQueries(moduleName, "Quiz");
const { data: pages } = usePagesQueries(moduleName);
const modal = useModal();
const moduleItems: {
type: "assignment" | "quiz" | "page";
item: IModuleItem;
}[] = assignments
}[] = (assignments ?? [])
.map(
(
a
@@ -51,7 +51,7 @@ export default function ExpandableModule({
item: a,
})
)
.concat(quizzes.map((q) => ({ type: "quiz", item: q })))
// .concat(quizzes.map((q) => ({ type: "quiz", item: q })))
.concat(pages.map((p) => ({ type: "page", item: p })))
.sort(
(a, b) =>

View File

@@ -4,7 +4,7 @@ import Providers from "./providers";
import { Suspense } from "react";
import { getQueryClient } from "./providersQueryClientUtils";
import { hydrateCourses } from "@/hooks/hookHydration";
import { dehydrate, HydrationBoundary } from "@tanstack/react-query";
import { dehydrate, hydrate, HydrationBoundary } from "@tanstack/react-query";
import { MyToaster } from "./MyToaster";
import { cookies } from "next/headers";
import { createServerSideHelpers } from "@trpc/react-query/server";
@@ -13,6 +13,7 @@ import { createTrpcContext } from "@/services/trpc/context";
import superjson from "superjson";
import { fileStorageService } from "@/services/fileStorage/fileStorageService";
import ClientOnly from "@/components/ClientOnly";
import { createTRPCQueryUtils } from "@trpc/react-query";
export const dynamic = "force-dynamic";
export const metadata: Metadata = {
@@ -24,47 +25,6 @@ export default async function RootLayout({
}: Readonly<{
children: React.ReactNode;
}>) {
const queryClient = getQueryClient();
await hydrateCourses(queryClient);
const dehydratedState = dehydrate(queryClient);
cookies(); // disables static page generation at build time
const trpcHelper = createServerSideHelpers({
router: trpcAppRouter,
ctx: createTrpcContext(),
transformer: superjson,
});
const allSettings = await fileStorageService.settings.getAllCoursesSettings();
await Promise.all(
allSettings.map(async (settings) => {
const courseName = settings.name;
const moduleNames = await fileStorageService.modules.getModuleNames(
courseName
);
await Promise.all(
moduleNames.map(async (moduleName) => {
await trpcHelper.assignment.getAllAssignments.prefetch({
courseName,
moduleName,
});
// await Promise.all(
// assignments.map(
// async (a) =>
// await trpcHelper.assignment.getAssignment.fetch({
// courseName,
// moduleName,
// assignmentName: a.name,
// })
// )
// );
})
);
})
);
const dehydratedTrpc = trpcHelper.dehydrate();
return (
<html lang="en">
<head></head>
@@ -73,11 +33,7 @@ export default async function RootLayout({
<MyToaster />
<Suspense>
<Providers>
<HydrationBoundary state={dehydratedTrpc}>
<HydrationBoundary state={dehydratedState}>
{children}
</HydrationBoundary>
</HydrationBoundary>
{children}
</Providers>
</Suspense>
</div>

View File

@@ -1,25 +1,70 @@
import { hydrateCourses } from "@/hooks/hookHydration";
import { fileStorageService } from "@/services/fileStorage/fileStorageService";
import { createTrpcContext } from "@/services/trpc/context";
import { trpcAppRouter } from "@/services/trpc/router/app";
import { dehydrate, HydrationBoundary } from "@tanstack/react-query";
import { createServerSideHelpers } from "@trpc/react-query/server";
import CourseList from "./CourseList";
import AddNewCourse from "./newCourse/AddNewCourse";
import TodaysLectures from "./todaysLectures/TodaysLectures";
import superjson from "superjson";
import { trpc } from "@/services/trpc/utils";
export default async function Home() {
const trpcHelper = createServerSideHelpers({
router: trpcAppRouter,
ctx: createTrpcContext(),
transformer: superjson,
});
const allSettings = await fileStorageService.settings.getAllCoursesSettings();
await Promise.all(
allSettings.map(async (settings) => {
const courseName = settings.name;
const moduleNames = await fileStorageService.modules.getModuleNames(
courseName
);
await Promise.all(
moduleNames.map(
async (moduleName) =>
await trpcHelper.assignment.getAllAssignments.fetch({
courseName,
moduleName,
})
)
);
})
);
await Promise.all(
allSettings.map(
async (settings) =>
await trpcHelper.lectures.getLectures.prefetch({ courseName: settings.name })
)
);
await hydrateCourses(trpcHelper.queryClient);
const dehydratedState = dehydrate(trpcHelper.queryClient);
// console.log("dehydratedState", dehydratedState);
return (
<main className="h-full flex justify-center overflow-auto">
<div className="xl:w-[900px] mx-auto">
<br />
<br />
<br />
<br />
<div className=" flex justify-center">
<CourseList />
<HydrationBoundary state={dehydratedState}>
<main className="h-full flex justify-center overflow-auto">
<div className="xl:w-[900px] mx-auto">
<br />
<br />
<br />
<br />
<div className=" flex justify-center">
<CourseList />
</div>
<br />
<br />
<TodaysLectures />
<br />
<br />
<AddNewCourse />
</div>
<br />
<br />
<TodaysLectures />
<br />
<br />
<AddNewCourse />
</div>
</main>
</main>
</HydrationBoundary>
);
}

View File

@@ -1,11 +1,15 @@
"use client";
import { QueryClientProvider } from "@tanstack/react-query";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { ReactNode } from "react";
import { getQueryClient } from "./providersQueryClientUtils";
import { SuspenseAndErrorHandling } from "@/components/SuspenseAndErrorHandling";
import TrpcProvider from "@/services/trpc/TrpcProvider";
export default function Providers({ children }: { children: ReactNode }) {
export default function Providers({
children,
}: {
children: ReactNode;
}) {
// NOTE: Avoid useState when initializing the query client if you don't
// have a suspense boundary between this and the code that may
// suspend because React will throw away the client on the initial

View File

@@ -1,19 +1,21 @@
"use client";
import { useLecturesByWeekQuery } from "@/hooks/localCourse/lectureHooks";
import { getDateOnlyMarkdownString } from "@/models/local/timeUtils";
import { getLecturePreviewUrl } from "@/services/urlUtils";
import Link from "next/link";
import { useCourseContext } from "../course/[courseName]/context/courseContext";
import { getLectureForDay } from "@/models/local/lectureUtils";
import { trpc } from "@/services/trpc/utils";
export default function OneCourseLectures() {
const { courseName } = useCourseContext();
const { data: weeks } = useLecturesByWeekQuery();
// const { data: weeks } = useLecturesByWeekQuery();
const [weeks] = trpc.lectures.getLectures.useSuspenseQuery({ courseName });
const dayAsDate = new Date();
const dayAsString = getDateOnlyMarkdownString(dayAsDate);
const todaysLecture = getLectureForDay(weeks, dayAsDate);
if (!todaysLecture) return <></>;
return (
<Link

View File

@@ -11,7 +11,7 @@ import { canvasQuizService } from "@/services/canvas/canvasQuizService";
import { canvasPageService } from "@/services/canvas/canvasPageService";
import { canvasQuizKeys } from "./canvas/canvasQuizHooks";
import { canvasPageKeys } from "./canvas/canvasPageHooks";
import { getLecturesQueryConfig } from "./localCourse/lectureHooks";
// import { getLecturesQueryConfig } from "./localCourse/lectureHooks";
// https://tanstack.com/query/latest/docs/framework/react/guides/ssr
export const hydrateCourses = async (queryClient: QueryClient) => {
@@ -61,7 +61,7 @@ export const hydrateCourse = async (
moduleNames.map((moduleName) => loadAllModuleData(courseName, moduleName))
);
await queryClient.prefetchQuery(getLecturesQueryConfig(courseName));
// await queryClient.prefetchQuery(getLecturesQueryConfig(courseName));
await queryClient.prefetchQuery({
queryKey: localCourseKeys.settings(courseName),

View File

@@ -35,6 +35,7 @@ export const useAssignmentQuery = (
export const useAssignmentsQuery = (moduleName: string) => {
const { courseName } = useCourseContext();
console.log("rendering all assignments query");
return trpc.assignment.getAllAssignments.useQuery({
moduleName,
courseName,

View File

@@ -13,16 +13,16 @@ import {
import { Lecture } from "@/models/local/lecture";
import { useLocalCourseSettingsQuery } from "./localCoursesHooks";
export const getLecturesQueryConfig = (courseName: string) =>
({
queryKey: lectureKeys.allLectures(courseName),
queryFn: async () => await getLectures(courseName),
} as const);
// export const getLecturesQueryConfig = (courseName: string) =>
// ({
// queryKey: lectureKeys.allLectures(courseName),
// queryFn: async () => await getLectures(courseName),
// } as const);
export const useLecturesByWeekQuery = () => {
const { courseName } = useCourseContext();
return useSuspenseQuery(getLecturesQueryConfig(courseName));
};
// export const useLecturesByWeekQuery = () => {
// const { courseName } = useCourseContext();
// return useSuspenseQuery(getLecturesQueryConfig(courseName));
// };
export const useLectureUpdateMutation = () => {
const { courseName } = useCourseContext();

View File

@@ -67,35 +67,35 @@ export const useAllCourseDataQuery = () => {
// }),
// });
const { data: quizzesAndModules } = useSuspenseQueries({
queries: moduleNames.map((moduleName) =>
getAllItemsQueryConfig(courseName, moduleName, "Quiz")
),
combine: (results) => ({
data: results.flatMap((r, i) =>
r.data.map((quiz) => ({
moduleName: moduleNames[i],
quiz,
}))
),
pending: results.some((r) => r.isPending),
}),
});
// const { data: quizzesAndModules } = useSuspenseQueries({
// queries: moduleNames.map((moduleName) =>
// getAllItemsQueryConfig(courseName, moduleName, "Quiz")
// ),
// combine: (results) => ({
// data: results.flatMap((r, i) =>
// r.data.map((quiz) => ({
// moduleName: moduleNames[i],
// quiz,
// }))
// ),
// pending: results.some((r) => r.isPending),
// }),
// });
const { data: pagesAndModules } = useSuspenseQueries({
queries: moduleNames.map((moduleName) =>
getAllItemsQueryConfig(courseName, moduleName, "Page")
),
combine: (results) => ({
data: results.flatMap((r, i) =>
r.data.map((page) => ({
moduleName: moduleNames[i],
page,
}))
),
pending: results.some((r) => r.isPending),
}),
});
// const { data: pagesAndModules } = useSuspenseQueries({
// queries: moduleNames.map((moduleName) =>
// getAllItemsQueryConfig(courseName, moduleName, "Page")
// ),
// combine: (results) => ({
// data: results.flatMap((r, i) =>
// r.data.map((page) => ({
// moduleName: moduleNames[i],
// page,
// }))
// ),
// pending: results.some((r) => r.isPending),
// }),
// });
return { assignmentsAndModules, quizzesAndModules, pagesAndModules };
return { assignmentsAndModules, quizzesAndModules: [], pagesAndModules: [] };
};

View File

@@ -11,11 +11,12 @@ export default function TrpcProvider({
children: React.ReactNode;
}) {
// NOTE: Your production URL environment variable may be different
const url = "/api/trpc";
// process.env.NEXT_PUBLIC_APP_DOMAIN &&
// !process.env.NEXT_PUBLIC_APP_DOMAIN.includes("localhost")
// ? `https://www.${process.env.NEXT_PUBLIC_APP_DOMAIN}/api/trpc/`
// : "http://localhost:3000/api/trpc/";
const url = "http://localhost:3000/api/trpc/"
//"/api/trpc";
// process.env.NEXT_PUBLIC_APP_DOMAIN &&
// !process.env.NEXT_PUBLIC_APP_DOMAIN.includes("localhost")
// ? `https://www.${process.env.NEXT_PUBLIC_APP_DOMAIN}/api/trpc/`
// : "http://localhost:3000/api/trpc/";
const [trpcClient] = useState(() =>
trpc.createClient({

View File

@@ -2,6 +2,7 @@ import { createTrpcContext } from "../context";
import publicProcedure from "../procedures/public";
import { createCallerFactory, router } from "../trpc";
import { assignmentRouter } from "./assignmentRouter";
import { lectureRouter } from "./lectureRouter";
export const helloRouter = router({
sayHello: publicProcedure.query(() => {
@@ -14,6 +15,7 @@ export const helloRouter = router({
export const trpcAppRouter = router({
hello: helloRouter,
assignment: assignmentRouter,
lectures: lectureRouter,
});
export const createCaller = createCallerFactory(trpcAppRouter);

View File

@@ -0,0 +1,15 @@
import { z } from "zod";
import publicProcedure from "../procedures/public";
import { router } from "../trpc";
import { getLectures } from "@/services/fileStorage/lectureFileStorageService";
export const lectureRouter = router({
getLectures: publicProcedure
.input(z.object({
courseName: z.string()
}))
.query(async ({input: {courseName}}) => {
return await getLectures(courseName)
})
})

View File

@@ -1,4 +1,50 @@
import { createTRPCReact } from "@trpc/react-query";
import { createTRPCReact, httpBatchLink } from "@trpc/react-query";
import { createTRPCNext } from "@trpc/next";
import { ssrPrepass } from '@trpc/next/ssrPrepass';
import { AppRouter } from "./router/app";
import superjson from "superjson";
export const trpc = createTRPCReact<AppRouter>();
// export const trpc = createTRPCNext<AppRouter>({
// ssr: true,
// ssrPrepass,
// transformer: superjson,
// config(opts) {
// const { ctx } = opts;
// if (typeof window !== "undefined") {
// // during client requests
// return {
// links: [
// httpBatchLink({
// url: "/api/trpc",
// transformer: superjson,
// }),
// ],
// };
// }
// return {
// links: [
// httpBatchLink({
// transformer: superjson,
// // The server needs to know your app's full url
// url: `http://localhost:3000/api/trpc`,
// /**
// * Set custom request headers on every request from tRPC
// * @see https://trpc.io/docs/v10/header
// */
// headers() {
// if (!ctx?.req?.headers) {
// return {};
// }
// // To use SSR properly, you need to forward client headers to the server
// // This is so you can pass through things like cookies when we're server-side rendering
// return {
// cookie: ctx.req.headers.cookie,
// };
// },
// }),
// ],
// };
// },
// });
// export const trpcClient = trpc.createClient({ links: [] }); //server only?