From 1d0b4f8b50213ac1ad35ac6d8d3151226134e6ec Mon Sep 17 00:00:00 2001 From: Warre Provoost <133233646+warreprovoost@users.noreply.github.com> Date: Sun, 12 May 2024 16:42:56 +0200 Subject: [PATCH] Fix flickering with data loader (#348) * added loader * added loader * linter * fix name --- .../components/Courses/AllCoursesTeacher.tsx | 11 +++- .../Courses/CourseUtilComponents.tsx | 58 +------------------ .../src/components/Courses/CourseUtils.tsx | 52 ++++++++++++++++- 3 files changed, 61 insertions(+), 60 deletions(-) diff --git a/frontend/src/components/Courses/AllCoursesTeacher.tsx b/frontend/src/components/Courses/AllCoursesTeacher.tsx index facfa9c2..12b736a8 100644 --- a/frontend/src/components/Courses/AllCoursesTeacher.tsx +++ b/frontend/src/components/Courses/AllCoursesTeacher.tsx @@ -3,7 +3,7 @@ import { useState } from "react"; import { useTranslation } from "react-i18next"; import { useNavigate } from "react-router-dom"; import { SideScrollableCourses } from "./CourseUtilComponents"; -import { Course, callToApiToCreateCourse } from "./CourseUtils"; +import {Course, callToApiToCreateCourse, ProjectDetail} from "./CourseUtils"; import { Title } from "../Header/Title"; import { useLoaderData } from "react-router-dom"; @@ -12,7 +12,12 @@ import { useLoaderData } from "react-router-dom"; */ export function AllCoursesTeacher(): JSX.Element { const [open, setOpen] = useState(false); - const courses = (useLoaderData() as Course[]); + const loader = useLoaderData() as { + courses: Course[]; + projects: { [courseId: string]: ProjectDetail[] } + }; + const courses = loader.courses; + const projects = loader.projects; const [courseName, setCourseName] = useState(''); const [error, setError] = useState(''); @@ -49,7 +54,7 @@ export function AllCoursesTeacher(): JSX.Element { <> - + {t('courseForm')}
diff --git a/frontend/src/components/Courses/CourseUtilComponents.tsx b/frontend/src/components/Courses/CourseUtilComponents.tsx index 36e0b13e..08495726 100644 --- a/frontend/src/components/Courses/CourseUtilComponents.tsx +++ b/frontend/src/components/Courses/CourseUtilComponents.tsx @@ -12,9 +12,7 @@ import { } from "@mui/material"; import { Course, - Project, ProjectDetail, - apiHost, getIdFromLink, getNearestFutureDate, } from "./CourseUtils"; @@ -22,7 +20,6 @@ import { Link, useNavigate, useLocation } from "react-router-dom"; import { useState, useEffect, useMemo } from "react"; import { useTranslation } from "react-i18next"; import debounce from "debounce"; -import { authenticatedFetch } from "../../utils/authenticated-fetch"; /** * @param text - The text to be displayed @@ -79,9 +76,10 @@ export function SearchBox({ * @returns A component to display courses in horizontal scroller where each course is a card containing its name. */ export function SideScrollableCourses({ - courses, + courses,projects }: { courses: Course[]; + projects: {[courseId: string]: ProjectDetail[];}; }): JSX.Element { //const navigate = useNavigate(); const location = useLocation(); @@ -101,9 +99,6 @@ export function SideScrollableCourses({ const [teacherNameFilter, setTeacherNameFilter] = useState( initialTeacherNameFilter ); - const [projects, setProjects] = useState<{ - [courseId: string]: ProjectDetail[]; - }>({}); const debouncedHandleSearchChange = useMemo( () => @@ -150,55 +145,6 @@ export function SideScrollableCourses({ setTeacherNameFilter(newTeacherNameFilter); }; - useEffect(() => { - // Fetch projects for each course - const fetchProjects = async () => { - const projectPromises = courses.map((course) => - authenticatedFetch( - `${apiHost}/projects?course_id=${getIdFromLink(course.course_id)}` - ).then((response) => response.json()) - ); - - const projectResults = await Promise.all(projectPromises); - const projectsMap: { [courseId: string]: ProjectDetail[] } = {}; - - projectResults.forEach((result, index) => { - const detailProjectPromises = result.data.map(async (item: Project) => { - const projectRes = await authenticatedFetch(item.project_id); - if (projectRes.status !== 200) { - throw new Response("Failed to fetch project data", { - status: projectRes.status, - }); - } - const projectJson = await projectRes.json(); - const projectData = projectJson.data; - let projectDeadlines = []; - if (projectData.deadlines) { - projectDeadlines = projectData.deadlines.map( - ([description, dateString]: [string, string]) => ({ - description, - date: new Date(dateString), - }) - ); - } - const project: ProjectDetail = { - ...item, - deadlines: projectDeadlines, - }; - return project; - }); - Promise.all(detailProjectPromises).then((projects) => { - projectsMap[getIdFromLink(courses[index].course_id)] = projects; - setProjects({ ...projectsMap }); - }); - }); - - setProjects(projectsMap); - }; - - fetchProjects(); - }, [courses]); - const filteredCourses = courses.filter( (course) => course.name.toLowerCase().includes(searchTerm.toLowerCase()) && diff --git a/frontend/src/components/Courses/CourseUtils.tsx b/frontend/src/components/Courses/CourseUtils.tsx index 756e264f..908ad41b 100644 --- a/frontend/src/components/Courses/CourseUtils.tsx +++ b/frontend/src/components/Courses/CourseUtils.tsx @@ -133,9 +133,59 @@ const fetchData = async (url: string, params?: URLSearchParams) => { export const dataLoaderCourses = async () => { //const params = new URLSearchParams({ 'teacher': loggedInUid() }); - return fetchData(`courses`); + + const courses = await fetchData(`courses`); + const projects = await fetchProjectsCourse(courses); + for( const c of courses){ + const teacher = await fetchData(`users/${c.teacher}`) + c.teacher = teacher.display_name + } + return {courses, projects} }; +/** + * Fetch the projects for the Course component + * @param courses - All the courses + * @returns the projects + */ +export async function fetchProjectsCourse (courses:Course[]) { + const projectPromises = courses.map((course) => + authenticatedFetch( + `${apiHost}/projects?course_id=${getIdFromLink(course.course_id)}` + ).then((response) => response.json()) + ); + + const projectResults = await Promise.all(projectPromises); + const projectsMap: { [courseId: string]: ProjectDetail[] } = {}; + for await (const [index, result] of projectResults.entries()) { + projectsMap[getIdFromLink(courses[index].course_id)] = await Promise.all(result.data.map(async (item: Project) => { + const projectRes = await authenticatedFetch(item.project_id); + if (projectRes.status !== 200) { + throw new Response("Failed to fetch project data", { + status: projectRes.status, + }); + } + const projectJson = await projectRes.json(); + const projectData = projectJson.data; + let projectDeadlines = []; + if (projectData.deadlines) { + projectDeadlines = projectData.deadlines.map( + ([description, dateString]: [string, string]) => ({ + description, + date: new Date(dateString), + }) + ); + } + const project: ProjectDetail = { + ...item, + deadlines: projectDeadlines, + }; + return project; + })); + } + return { ...projectsMap }; +} + const dataLoaderCourse = async (courseId: string) => { return fetchData(`courses/${courseId}`); };