Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tanstack Query Migration #182

Merged
merged 17 commits into from
Nov 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
208 changes: 94 additions & 114 deletions apps/frontend/src/app/api/course.ts
Original file line number Diff line number Diff line change
@@ -1,40 +1,10 @@
import { createAsyncThunk } from "@reduxjs/toolkit";
import axios from "axios";
import { useQuery, useQueries, keepPreviousData } from "@tanstack/react-query";
import { create, windowScheduler, keyResolver } from "@yornaath/batshit"
import { Course, Session } from "~/types";
import { RootState } from "~/store";

export const fetchCourseInfos = createAsyncThunk<
Course[],
string[],
{ state: RootState }
>("fetchCourseInfos", async (ids: string[], thunkAPI) => {
const state = thunkAPI.getState();

const newIds = ids.filter((id) => !(id in state.cache.courseResults));
if (newIds.length === 0) return [];

const url = `${process.env.NEXT_PUBLIC_BACKEND_URL || ""}/courses?`;
const params = new URLSearchParams(ids.map((id) => ["courseID", id]));

params.set("schedules", "true");

if (state.user.loggedIn) {
params.set("fces", "true");

return (
await fetch(url + params.toString(), {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
token: state.user.token,
}),
})
).json();
} else {
return (await fetch(url + params.toString())).json();
}
});
import { STALE_TIME } from "~/app/constants";
import { FiltersState } from "~/app/filters";
import { useAppSelector } from "~/app/hooks";

export type FetchCourseInfosByPageResult = {
docs: Course[];
Expand All @@ -49,113 +19,123 @@ export type FetchCourseInfosByPageResult = {
nextPage: number | null;
};

export const fetchCourseInfosByPage = createAsyncThunk<
FetchCourseInfosByPageResult,
number,
{ state: RootState }
>("fetchCourseInfosByPage", async (page: number, thunkAPI) => {
const state = thunkAPI.getState();

const fetchCourseInfosByPage = async (filters: FiltersState, page: number): Promise<FetchCourseInfosByPageResult> => {
const url = `${process.env.NEXT_PUBLIC_BACKEND_URL || ""}/courses/search?`;
const params = new URLSearchParams({
page: `${page}`,
schedules: "true",
});

if (state.filters.search !== "") {
params.set("keywords", state.filters.search);
if (filters.search !== "") {
params.set("keywords", filters.search);
}

if (
state.filters.departments.active &&
state.filters.departments.names.length > 0
) {
state.filters.departments.names.forEach((d) =>
params.append("department", d)
);
if (filters.departments.active && filters.departments.names.length > 0) {
filters.departments.names.forEach((d) => params.append("department", d));
}

if (state.filters.units.active) {
params.append("unitsMin", state.filters.units.min.toString());
params.append("unitsMax", state.filters.units.max.toString());
if (filters.units.active) {
params.append("unitsMin", filters.units.min.toString());
params.append("unitsMax", filters.units.max.toString());
}

if (state.filters.semesters.active) {
state.filters.semesters.sessions.forEach((s: Session) =>
params.append("session", JSON.stringify(s))
);
if (filters.semesters.active) {
filters.semesters.sessions.forEach((s: Session) => params.append("session", JSON.stringify(s)));
}

if (state.filters.levels.active) {
if (filters.levels.active) {
let value = "";
state.filters.levels.selected.forEach((elem, index) => {
filters.levels.selected.forEach((elem, index) => {
if (elem) value += index.toString();
});

if (value) params.append("levels", value);
}

if (state.user.loggedIn) {
return (
await fetch(url + params.toString(), {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
token: state.user.token,
}),
})
).json();
} else {
return (await fetch(url + params.toString())).json();
}
});
const response = await axios.get(url, {
headers: {
"Content-Type": "application/json",
},
params,
});

type FetchCourseInfoOptions = {
courseID: string;
schedules: boolean;
return response.data;
};

export const fetchCourseInfo = createAsyncThunk<
Course,
FetchCourseInfoOptions,
{ state: RootState }
>(
"fetchCourseInfo",
async ({ courseID, schedules }: FetchCourseInfoOptions, thunkAPI) => {
if (!courseID) return;

const state = thunkAPI.getState();
if (courseID in state.cache.courseResults && !schedules) return;

const url = `${process.env.NEXT_PUBLIC_BACKEND_URL || ""}/course/${courseID}?`;
const params = new URLSearchParams({
schedules: schedules ? "true" : "false",
});

return (await fetch(url + params.toString())).json();
}
);
export const useFetchCourseInfosByPage = () => {
const filters = useAppSelector((state) => state.filters);
const page = useAppSelector((state) => state.filters.page);

type FetchAllCoursesType = { name: string; courseID: string }[];
return useQuery({
queryKey: ['courseInfosByPage', filters, page],
queryFn: () => fetchCourseInfosByPage(filters, page),
staleTime: STALE_TIME,
placeholderData: keepPreviousData,
});
};

export const fetchAllCourses = createAsyncThunk<
FetchAllCoursesType,
void,
{ state: RootState }
>("fetchAllCourses", async (_, thunkAPI) => {
const url = `${process.env.NEXT_PUBLIC_BACKEND_URL || ""}/courses/all`;
const state = thunkAPI.getState();
const fetchCourseInfosBatcher = create({
fetcher: async (courseIDs: string[]): Promise<Course[]> => {
const url = `${process.env.NEXT_PUBLIC_BACKEND_URL || ""}/courses?`;
const params = new URLSearchParams(courseIDs.map((id) => ["courseID", id]));

if (state.cache.allCourses.length > 0) return;
params.set("schedules", "true");

return (
await fetch(url, {
method: "GET",
const response = await axios.get(url, {
headers: {
"Content-Type": "application/json",
},
})
).json();
params,
});

return response.data;
},
resolver: keyResolver("courseID"),
scheduler: windowScheduler(10),
});

export const useFetchCourseInfo = (courseID: string) => {
return useQuery({
queryKey: ['courseInfo', courseID],
queryFn: () => fetchCourseInfosBatcher.fetch(courseID),
staleTime: STALE_TIME,
});
};

export const useFetchCourseInfos = (courseIDs: string[]) => {
return useQueries({
queries: courseIDs.map((courseID) => ({
queryKey: ['courseInfo', courseID],
queryFn: () => fetchCourseInfosBatcher.fetch(courseID),
staleTime: STALE_TIME,
})),
combine: result => {
return result.reduce((acc, { data }) => {
if (data) acc.push(data);
return acc;
}, [] as Course[]);
},
});
};

type FetchAllCoursesType = { name: string; courseID: string }[];

const fetchAllCourses = async (): Promise<FetchAllCoursesType> => {
const url = `${process.env.NEXT_PUBLIC_BACKEND_URL || ""}/courses/all`;

const response = await axios.get(url, {
headers: {
"Content-Type": "application/json",
},
});

return response.data;
};

export const useFetchAllCourses = () => {
return useQuery({
queryKey: ['allCourses'],
queryFn: fetchAllCourses,
staleTime: STALE_TIME,
});
};
127 changes: 78 additions & 49 deletions apps/frontend/src/app/api/fce.ts
Original file line number Diff line number Diff line change
@@ -1,63 +1,92 @@
import { createAsyncThunk } from "@reduxjs/toolkit";
import axios from "axios";
import { FCE } from "~/types";
import { RootState } from "~/store";
import { GetToken } from "@clerk/types";
import { useQueries, useQuery } from "@tanstack/react-query";
import { STALE_TIME } from "~/app/constants";
import { create, keyResolver, windowScheduler } from "@yornaath/batshit";
import { memoize } from "lodash-es";
import { useAuth } from "@clerk/nextjs";

type FCEInfosOptions = { courseIDs: string[] };
const fetchFCEInfosByCourseBatcher = memoize((isSignedIn: boolean | undefined, getToken: GetToken) => {
return create({
fetcher: async (courseIDs: string[]): Promise<{ courseID: string; fces: FCE[]; }[]> => {
const url = `${process.env.NEXT_PUBLIC_BACKEND_URL || ""}/fces`;
const params = new URLSearchParams();

export const fetchFCEInfosByCourse = createAsyncThunk<
FCE[],
FCEInfosOptions,
{ state: RootState }
>("fetchFCEInfosByCourse", async ({ courseIDs }: FCEInfosOptions, thunkAPI) => {
const state = thunkAPI.getState();
courseIDs.forEach((courseID) => params.append("courseID", courseID));

const newIds = courseIDs.filter((id) => !(id in state.cache.fces));
if (newIds.length === 0) return;
const token = await getToken();

const url = `${process.env.NEXT_PUBLIC_BACKEND_URL || ""}/fces?`;
const params = new URLSearchParams();
if (isSignedIn && token) {
const response = await axios.post(url, {token}, {
headers: {
"Content-Type": "application/json",
},
params,
});

newIds.forEach((courseID) => params.append("courseID", courseID));

if (state.user.loggedIn && state.user.token) {
return (
await fetch(url + params.toString(), {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
token: state.user.token,
}),
})
).json();
}
return courseIDs.map((courseID) => ({
courseID,
fces: response.data.filter((fce: FCE) => fce.courseID === courseID)
}));
}
return courseIDs.map((courseID) => ({courseID, fces: []}));
},
resolver: keyResolver("courseID"),
scheduler: windowScheduler(10),
});
});

export const fetchFCEInfosByInstructor = createAsyncThunk<
FCE[],
string,
{ state: RootState }
>("fetchFCEInfosByInstructor", async (instructor: string, thunkAPI) => {
const state = thunkAPI.getState();
export const useFetchFCEInfoByCourse = (courseID: string) => {
const { isSignedIn, getToken } = useAuth();

return useQuery({
queryKey: ['fces', courseID, isSignedIn],
queryFn: () => fetchFCEInfosByCourseBatcher(isSignedIn, getToken).fetch(courseID),
staleTime: STALE_TIME,
});
};

if (instructor in state.cache.instructorResults) return;
export const useFetchFCEInfosByCourse = (courseIDs: string[], isSignedIn: boolean | undefined, getToken: GetToken) => {
return useQueries({
queries: courseIDs.map((courseID) => ({
queryKey: ['fces', courseID, isSignedIn],
queryFn: () => fetchFCEInfosByCourseBatcher(isSignedIn, getToken).fetch(courseID),
staleTime: STALE_TIME,
placeholderData: {courseID, fces: []},
})),
combine: result => {
return result.reduce((acc, {data}) => {
if (data) acc.push(data);
return acc;
}, [] as FCE[]);
},
});
};

const url = `${process.env.NEXT_PUBLIC_BACKEND_URL || ""}/fces?`;
const fetchFCEInfosByInstructor = async (instructor: string, isSignedIn: boolean | undefined, getToken: GetToken): Promise<FCE[]> => {
const url = `${process.env.NEXT_PUBLIC_BACKEND_URL || ""}/fces`;
const params = new URLSearchParams();
params.append("instructor", instructor);

if (state.user.loggedIn && state.user.token) {
return (
await fetch(url + params.toString(), {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
token: state.user.token,
}),
})
).json();
const token = await getToken();

if (isSignedIn && token) {
const response = await axios.post(url, { token }, {
headers: {
"Content-Type": "application/json",
},
params,
});
return response.data;
}
});
return [];
};

export const useFetchFCEInfosByInstructor = (instructor: string, isSignedIn: boolean | undefined, getToken: GetToken) => {
return useQuery({
queryKey: ['instructorFCEs', instructor, isSignedIn],
queryFn: () => fetchFCEInfosByInstructor(instructor, isSignedIn, getToken),
staleTime: STALE_TIME,
});
};
Loading
Loading