From cfd82d3c422b3520ed57230c1ccbf1e4446356e0 Mon Sep 17 00:00:00 2001 From: Keannu Bernasol Date: Tue, 15 Aug 2023 00:41:42 +0800 Subject: [PATCH] Refactored error handling in API functions and improved error feedback in pages --- App.tsx | 4 +- src/components/Api/Api.tsx | 74 ++++++++-------------- src/components/ParseError/ParseError.tsx | 24 ------- src/icons/InfoIcon/InfoIcon.tsx | 27 ++++++++ src/routes/Home/Home.tsx | 15 +++-- src/routes/Login/Login.tsx | 2 - src/routes/Onboarding/Onboarding.tsx | 36 ++++++++--- src/routes/StartStudying/StartStudying.tsx | 25 ++++++-- src/routes/SubjectsPage/SubjectsPage.tsx | 41 ++++++++++-- src/routes/UserInfoPage/UserInfoPage.tsx | 49 ++++++++++---- 10 files changed, 181 insertions(+), 116 deletions(-) delete mode 100644 src/components/ParseError/ParseError.tsx create mode 100644 src/icons/InfoIcon/InfoIcon.tsx diff --git a/App.tsx b/App.tsx index 7130bbe..38ae918 100644 --- a/App.tsx +++ b/App.tsx @@ -25,7 +25,7 @@ import SubjectsPage from "./src/routes/SubjectsPage/SubjectsPage"; import Loading from "./src/routes/Loading/Loading"; import StartStudying from "./src/routes/StartStudying/StartStudying"; import { ToastProvider } from "react-native-toast-notifications"; -import AppIcon from "./src/icons/AppIcon/AppIcon"; +import InfoIcon from "./src/icons/InfoIcon/InfoIcon"; const Drawer = createDrawerNavigator(); @@ -61,7 +61,7 @@ export default function App() { }, [initialRoute]); return ( } + icon={} textStyle={{ ...styles.text_white_tiny_bold }} > diff --git a/src/components/Api/Api.tsx b/src/components/Api/Api.tsx index 8524c32..bd53dd2 100644 --- a/src/components/Api/Api.tsx +++ b/src/components/Api/Api.tsx @@ -35,6 +35,22 @@ export const urlProvider = "https://tile.thunderforest.com/atlas/{z}/{x}/{y}.png?apikey=0f5cb5930d7642a8a921daea650754d9"; // App APIs +// Error Handling +export function ParseError(error: any) { + if (error.response && error.response.data) { + return JSON.stringify(error.response.data) + .replaceAll(/[{}()"]/g, " ") + .replaceAll(/,/g, "\n") + .replaceAll("[", "") + .replaceAll("]", "") + .replaceAll(".", "") + .replaceAll(/"/g, "") + .replaceAll("non_field_errors", "") + .trim(); + } + return "Unable to reach server"; +} + // Token Handling export async function getAccessToken() { const accessToken = await AsyncStorage.getItem("access_token"); @@ -75,9 +91,7 @@ export function UserRegister(register: RegistrationType) { return [true, response.status]; }) .catch((error) => { - let error_message = ""; - if (error.response) error_message = error.response.data; - else error_message = "Unable to reach servers"; + let error_message = ParseError(error); return [false, error_message]; }); } @@ -86,21 +100,12 @@ export function UserLogin(user: LoginType) { return instance .post("/api/v1/accounts/jwt/create/", user) .then(async (response) => { - /*console.log( - "Access Token:", - response.data.access, - "\nRefresh Token:", - response.data.refresh - );*/ setAccessToken(response.data.access); setRefreshToken(response.data.refresh); return [true]; }) .catch((error) => { - let error_message = ""; - if (error.response) error_message = error.response.data; - else error_message = "Unable to reach servers"; - // console.log(error_message); + let error_message = ParseError(error); return [false, error_message]; }); } @@ -114,17 +119,10 @@ export async function TokenRefresh() { }) .then(async (response) => { setAccessToken(response.data.access); - /*console.log( - "Token refresh success! New Access Token", - response.data.access - );*/ return true; }) .catch((error) => { - let error_message = ""; - if (error.response) error_message = error.response.data; - else error_message = "Unable to reach servers"; - console.log("Token Refresh error:", error_message); + let error_message = ParseError(error); return false; }); } @@ -137,9 +135,7 @@ export async function GetUserInfo() { return [true, response.data]; }) .catch((error) => { - let error_message = ""; - if (error.response) error_message = error.response.data; - else error_message = "Unable to reach servers"; + let error_message = ParseError(error); return [false, error_message]; }); } @@ -153,9 +149,7 @@ export async function PatchUserInfo(info: PatchUserInfoType) { return [true, response.data]; }) .catch((error) => { - let error_message = ""; - if (error.response) error_message = error.response.data; - else error_message = "Unable to reach servers"; + let error_message = ParseError(error); return [false, error_message]; }); } @@ -186,9 +180,7 @@ export async function GetCourses() { return [true, response.data]; }) .catch((error) => { - let error_message = ""; - if (error.response) error_message = error.response.data; - else error_message = "Unable to reach servers"; + let error_message = ParseError(error); return [false, error_message]; }); } @@ -206,9 +198,7 @@ export async function GetSemesters() { return [true, response.data]; }) .catch((error) => { - let error_message = ""; - if (error.response) error_message = error.response.data; - else error_message = "Unable to reach servers"; + let error_message = ParseError(error); return [false, error_message]; }); } @@ -222,9 +212,7 @@ export async function GetYearLevels() { return [true, response.data]; }) .catch((error) => { - let error_message = ""; - if (error.response) error_message = error.response.data; - else error_message = "Unable to reach servers"; + let error_message = ParseError(error); return [false, error_message]; }); } @@ -237,9 +225,7 @@ export async function GetSubjects() { return [true, response.data]; }) .catch((error) => { - let error_message = ""; - if (error.response) error_message = error.response.data; - else error_message = "Unable to reach servers"; + let error_message = ParseError(error); return [false, error_message]; }); } @@ -252,10 +238,7 @@ export async function GetStudentStatus() { return [true, response.data]; }) .catch((error) => { - let error_message = ""; - if (error.response) error_message = error.response.data; - else error_message = "Unable to reach servers"; - console.log(error_message); + let error_message = ParseError(error); return [false, error_message]; }); } @@ -269,10 +252,7 @@ export async function PatchStudentStatus(info: StudentStatusType) { return [true, response.data]; }) .catch((error) => { - let error_message = ""; - if (error.response) error_message = error.response.data; - else error_message = "Unable to reach servers"; - console.log(error_message); + let error_message = ParseError(error); return [false, error_message]; }); } diff --git a/src/components/ParseError/ParseError.tsx b/src/components/ParseError/ParseError.tsx deleted file mode 100644 index ab55ea6..0000000 --- a/src/components/ParseError/ParseError.tsx +++ /dev/null @@ -1,24 +0,0 @@ -export default function ParseError(text: string) { - if (text) { - return text - .replaceAll(/[{}()"]/g, " ") - .replaceAll(/,/g, "\n") - .replaceAll("[", "") - .replaceAll("]", "") - .replaceAll(".", ""); - } - return ""; -} - -export function ParseLoginError(text: string) { - if (text) { - return text - .replaceAll(/[{}()"]/g, " ") - .replaceAll(/,/g, "\n") - .replaceAll("[", "") - .replaceAll("]", "") - .replaceAll(".", "") - .replaceAll("non_field_errors", ""); - } - return ""; -} diff --git a/src/icons/InfoIcon/InfoIcon.tsx b/src/icons/InfoIcon/InfoIcon.tsx new file mode 100644 index 0000000..6e60500 --- /dev/null +++ b/src/icons/InfoIcon/InfoIcon.tsx @@ -0,0 +1,27 @@ +import * as React from "react"; +import { IconProps } from "../../interfaces/Interfaces"; + +import { Svg, Path } from "react-native-svg"; +import { colors } from "../../styles"; + +export default function InfoIcon(props: IconProps) { + return ( + <> + + + + + + + + ); +} diff --git a/src/routes/Home/Home.tsx b/src/routes/Home/Home.tsx index 58e1509..5230ffa 100644 --- a/src/routes/Home/Home.tsx +++ b/src/routes/Home/Home.tsx @@ -108,7 +108,7 @@ export default function Home() { queryFn: async () => { const data = await GetStudentStatus(); if (data[0] == false) { - return Promise.reject(new Error()); + return Promise.reject(new Error(JSON.stringify(data[1]))); } return data; }, @@ -126,8 +126,13 @@ export default function Home() { } console.log(data[1]); }, - onError: () => { - setFeedback("Unable to query available subjects"); + onError: (error: Error) => { + toast.show(String(error), { + type: "warning", + placement: "top", + duration: 2000, + animationType: "slide-in", + }); }, }); @@ -149,8 +154,8 @@ export default function Home() { animationType: "slide-in", }); }, - onError: () => { - toast.show("Server error. Unable to update student status", { + onError: (error: Error) => { + toast.show(String(error), { type: "warning", placement: "top", duration: 2000, diff --git a/src/routes/Login/Login.tsx b/src/routes/Login/Login.tsx index 22430cf..b141a3d 100644 --- a/src/routes/Login/Login.tsx +++ b/src/routes/Login/Login.tsx @@ -14,7 +14,6 @@ import Button from "../../components/Button/Button"; import { useNavigation } from "@react-navigation/native"; import { RootDrawerParamList } from "../../interfaces/Interfaces"; import { GetUserInfo, UserLogin } from "../../components/Api/Api"; -import { ParseLoginError } from "../../components/ParseError/ParseError"; import AnimatedContainer from "../../components/AnimatedContainer/AnimatedContainer"; import { setUser } from "../../features/redux/slices/UserSlice/UserSlice"; import { @@ -104,7 +103,6 @@ export default function Login() { } console.log(JSON.stringify(user_info)); } else { - console.log(ParseLoginError(JSON.stringify(result[1]))); toast.show(JSON.stringify(result[1]), { type: "warning", placement: "top", diff --git a/src/routes/Onboarding/Onboarding.tsx b/src/routes/Onboarding/Onboarding.tsx index 5e9ea3a..89952de 100644 --- a/src/routes/Onboarding/Onboarding.tsx +++ b/src/routes/Onboarding/Onboarding.tsx @@ -39,7 +39,13 @@ export default function Onboarding() { ]); const semester_query = useQuery({ queryKey: ["semesters"], - queryFn: GetSemesters, + queryFn: async () => { + const data = await GetSemesters(); + if (data[0] == false) { + return Promise.reject(new Error(data[1])); + } + return data; + }, onSuccess: (data) => { let semesters = data[1].map((item: SemesterType) => ({ label: item.name, @@ -47,8 +53,8 @@ export default function Onboarding() { })); setSemesters(semesters); }, - onError: () => { - toast.show("Server error: Unable to query available semesters", { + onError: (error: Error) => { + toast.show(String(error), { type: "warning", placement: "top", duration: 2000, @@ -65,7 +71,13 @@ export default function Onboarding() { ]); const yearlevel_query = useQuery({ queryKey: ["year_levels"], - queryFn: GetYearLevels, + queryFn: async () => { + const data = await GetYearLevels(); + if (data[0] == false) { + return Promise.reject(new Error(data[1])); + } + return data; + }, onSuccess: (data) => { let year_levels = data[1].map((item: YearLevelType) => ({ label: item.name, @@ -73,8 +85,8 @@ export default function Onboarding() { })); setYearLevels(year_levels); }, - onError: () => { - toast.show("Server error: Unable to query available year levels", { + onError: (error: Error) => { + toast.show(String(error), { type: "warning", placement: "top", duration: 2000, @@ -94,7 +106,13 @@ export default function Onboarding() { ]); const course_query = useQuery({ queryKey: ["courses"], - queryFn: GetCourses, + queryFn: async () => { + const data = await GetCourses(); + if (data[0] == false) { + return Promise.reject(new Error(data[1])); + } + return data; + }, onSuccess: (data) => { let courses = data[1].map((item: CourseType) => ({ label: item.name, @@ -102,8 +120,8 @@ export default function Onboarding() { })); setCourses(courses); }, - onError: () => { - toast.show("Server error: Unable to query available courses", { + onError: (error: Error) => { + toast.show(String(error), { type: "warning", placement: "top", duration: 2000, diff --git a/src/routes/StartStudying/StartStudying.tsx b/src/routes/StartStudying/StartStudying.tsx index cea7bb9..9f420d2 100644 --- a/src/routes/StartStudying/StartStudying.tsx +++ b/src/routes/StartStudying/StartStudying.tsx @@ -7,10 +7,15 @@ import { OptionType, RootDrawerParamList, StudentStatusType, + StudentStatusReturnType, } from "../../interfaces/Interfaces"; import Button from "../../components/Button/Button"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; -import { PatchStudentStatus, GetUserInfo } from "../../components/Api/Api"; +import { + PatchStudentStatus, + GetUserInfo, + ParseError, +} from "../../components/Api/Api"; import { colors } from "../../styles"; import DropDownPicker from "react-native-dropdown-picker"; import AnimatedContainerNoScroll from "../../components/AnimatedContainer/AnimatedContainerNoScroll"; @@ -31,7 +36,13 @@ export default function StartStudying({ route }: any) { const [subjects, setSubjects] = useState([]); const StudentInfo = useQuery({ queryKey: ["user"], - queryFn: GetUserInfo, + queryFn: async () => { + const data = await GetUserInfo(); + if (data[0] == false) { + return Promise.reject(new Error(data[1])); + } + return data; + }, onSuccess: (data: UserInfoReturnType) => { let subjects = data[1].subjects.map((subject: string) => ({ label: subject, @@ -39,8 +50,8 @@ export default function StartStudying({ route }: any) { })); setSubjects(subjects); }, - onError: () => { - toast.show("Server error: Unable to query available subjects", { + onError: (error: Error) => { + toast.show(String(error), { type: "warning", placement: "top", duration: 2000, @@ -53,7 +64,7 @@ export default function StartStudying({ route }: any) { mutationFn: async (info: StudentStatusType) => { const data = await PatchStudentStatus(info); if (data[0] == false) { - return Promise.reject(new Error("Error updating student status")); + return Promise.reject(new Error(JSON.stringify(data[1]))); } return data; }, @@ -68,8 +79,8 @@ export default function StartStudying({ route }: any) { }); navigation.navigate("Home"); }, - onError: () => { - toast.show("A server error has occured. Please try again", { + onError: (error: Error) => { + toast.show(String(error), { type: "warning", placement: "top", duration: 2000, diff --git a/src/routes/SubjectsPage/SubjectsPage.tsx b/src/routes/SubjectsPage/SubjectsPage.tsx index a43dc33..76c841d 100644 --- a/src/routes/SubjectsPage/SubjectsPage.tsx +++ b/src/routes/SubjectsPage/SubjectsPage.tsx @@ -8,6 +8,7 @@ import { SubjectType, OptionType, StudentStatusType, + PatchUserInfoType, } from "../../interfaces/Interfaces"; import Button from "../../components/Button/Button"; import { Image } from "react-native"; @@ -69,7 +70,13 @@ export default function SubjectsPage() { }); const StudentInfo = useQuery({ queryKey: ["user"], - queryFn: GetUserInfo, + queryFn: async () => { + const data = await GetUserInfo(); + if (data[0] == false) { + return Promise.reject(new Error(data[1])); + } + return data; + }, onSuccess: (data: UserInfoReturnType) => { setUser({ ...user, @@ -84,8 +91,8 @@ export default function SubjectsPage() { }); setSelectedSubjects(data[1].subjects); }, - onError: () => { - toast.show("Server Error: Unable to query user info", { + onError: (error: Error) => { + toast.show(String(error), { type: "warning", placement: "top", duration: 2000, @@ -94,7 +101,13 @@ export default function SubjectsPage() { }, }); const mutation = useMutation({ - mutationFn: PatchUserInfo, + mutationFn: async (info: PatchUserInfoType) => { + const data = await PatchUserInfo(info); + if (data[0] != true) { + return Promise.reject(new Error()); + } + return data; + }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ["user"] }); queryClient.invalidateQueries({ queryKey: ["subjects"] }); @@ -110,6 +123,14 @@ export default function SubjectsPage() { animationType: "slide-in", }); }, + onError: (error: Error) => { + toast.show(String(error), { + type: "warning", + placement: "top", + duration: 2000, + animationType: "slide-in", + }); + }, }); // Subjects @@ -121,7 +142,13 @@ export default function SubjectsPage() { const Subjects = useQuery({ enabled: StudentInfo.isFetched, queryKey: ["subjects"], - queryFn: GetSubjects, + queryFn: async () => { + const data = await GetSubjects(); + if (data[0] == false) { + return Promise.reject(new Error(JSON.stringify(data[1]))); + } + return data; + }, onSuccess: (data: SubjectsReturnType) => { if (data[1]) { let subjects = data[1].map((subject: SubjectType) => ({ @@ -132,8 +159,8 @@ export default function SubjectsPage() { setSubjects(subjects); } }, - onError: () => { - toast.show("Server Error: Unable to query subject info", { + onError: (error: Error) => { + toast.show(String(error), { type: "warning", placement: "top", duration: 2000, diff --git a/src/routes/UserInfoPage/UserInfoPage.tsx b/src/routes/UserInfoPage/UserInfoPage.tsx index 1bd0d34..512ea89 100644 --- a/src/routes/UserInfoPage/UserInfoPage.tsx +++ b/src/routes/UserInfoPage/UserInfoPage.tsx @@ -17,7 +17,6 @@ import { CourseType, OptionType, StudentStatusType, - UserInfoType, PatchUserInfoType, } from "../../interfaces/Interfaces"; import Button from "../../components/Button/Button"; @@ -88,7 +87,13 @@ export default function UserInfoPage() { }); const StudentInfo = useQuery({ queryKey: ["user"], - queryFn: GetUserInfo, + queryFn: async () => { + const data = await GetUserInfo(); + if (data[0] == false) { + return Promise.reject(new Error(data[1])); + } + return data; + }, onSuccess: (data: UserInfoReturnType) => { // console.log(data[1]); setUser({ @@ -107,8 +112,8 @@ export default function UserInfoPage() { setSelectedYearLevel(data[1].year_level); dispatch(setUserinState(data[1])); }, - onError: () => { - toast.show("Server Error: Unable to query user info", { + onError: (error: Error) => { + toast.show(String(error), { type: "warning", placement: "top", duration: 2000, @@ -156,7 +161,13 @@ export default function UserInfoPage() { const [semesters, setSemesters] = useState([]); const Semesters = useQuery({ queryKey: ["semesters"], - queryFn: GetSemesters, + queryFn: async () => { + const data = await GetSemesters(); + if (data[0] == false) { + return Promise.reject(new Error(data[1])); + } + return data; + }, onSuccess: (data: SemesterReturnType) => { let semestersData = data[1].map((semester: SemesterType) => ({ label: semester.name, @@ -166,8 +177,8 @@ export default function UserInfoPage() { // Update the 'semesters' state setSemesters(semestersData); }, - onError: () => { - toast.show("Server Error: Unable to query semester info", { + onError: (error: Error) => { + toast.show(String(error), { type: "warning", placement: "top", duration: 2000, @@ -182,7 +193,13 @@ export default function UserInfoPage() { const [year_levels, setYearLevels] = useState([]); const yearlevel_query = useQuery({ queryKey: ["year_levels"], - queryFn: GetYearLevels, + queryFn: async () => { + const data = await GetYearLevels(); + if (data[0] == false) { + return Promise.reject(new Error(data[1])); + } + return data; + }, onSuccess: (data) => { let year_levels = data[1].map((yearlevel: YearLevelType) => ({ label: yearlevel.name, @@ -190,8 +207,8 @@ export default function UserInfoPage() { })); setYearLevels(year_levels); }, - onError: () => { - toast.show("Server Error: Unable to query year level info", { + onError: (error: Error) => { + toast.show(String(error), { type: "warning", placement: "top", duration: 2000, @@ -206,7 +223,13 @@ export default function UserInfoPage() { const [courses, setCourses] = useState([]); const course_query = useQuery({ queryKey: ["courses"], - queryFn: GetCourses, + queryFn: async () => { + const data = await GetCourses(); + if (data[0] == false) { + return Promise.reject(new Error(data[1])); + } + return data; + }, onSuccess: (data) => { let courses = data[1].map((course: CourseType) => ({ label: course.name, @@ -214,8 +237,8 @@ export default function UserInfoPage() { })); setCourses(courses); }, - onError: () => { - toast.show("Server Error: Unable to query course info", { + onError: (error: Error) => { + toast.show(String(error), { type: "warning", placement: "top", duration: 2000,