Refactored error handling in API functions and improved error feedback in pages

This commit is contained in:
Keannu Bernasol 2023-08-15 00:41:42 +08:00
parent c4c11d1afe
commit cfd82d3c42
10 changed files with 181 additions and 116 deletions

View file

@ -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 (
<ToastProvider
icon={<AppIcon size={64} />}
icon={<InfoIcon size={32} />}
textStyle={{ ...styles.text_white_tiny_bold }}
>
<QueryClientProvider client={queryClient}>

View file

@ -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];
});
}

View file

@ -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 "";
}

View file

@ -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 (
<>
<Svg
width={props.size}
height={props.size}
viewBox="0 0 24 24"
strokeWidth="2"
stroke={colors.icon_color}
fill="none"
strokeLinecap="round"
strokeLinejoin="round"
>
<Path stroke="none" d="M0 0h24v24H0z" fill="none"></Path>
<Path d="M12 9h.01"></Path>
<Path d="M11 12h1v4h1"></Path>
<Path d="M12 3c7.2 0 9 1.8 9 9s-1.8 9 -9 9s-9 -1.8 -9 -9s1.8 -9 9 -9z"></Path>
</Svg>
</>
);
}

View file

@ -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,

View file

@ -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",

View file

@ -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,

View file

@ -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<OptionType[]>([]);
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,

View file

@ -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,

View file

@ -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<OptionType[]>([]);
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<OptionType[]>([]);
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<OptionType[]>([]);
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,