refactor: refactored course & lesson query hooks

This commit is contained in:
Sergey Yarkov 2021-12-01 23:58:30 +03:00
parent 2fa555b8be
commit dd86228cab
11 changed files with 178 additions and 120 deletions

6
package-lock.json generated
View File

@ -2552,9 +2552,9 @@
"integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw=="
},
"typescript": {
"version": "4.4.4",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.4.4.tgz",
"integrity": "sha512-DqGhF5IKoBl8WNf8C1gu8q0xZSInh9j1kJJMqT3a94w1JzVaBU4EXOSMrz9yDqMT0xt3selp83fuFMQ0uzv6qA==",
"version": "4.5.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.2.tgz",
"integrity": "sha512-5BlMof9H1yGt0P8/WF+wPNw6GfctgGjXp5hkblpyT+8rkASSmkUKMXrxR0Xg8ThVCi/JnHQiKXeBaEwCeQwMFw==",
"dev": true
},
"use-callback-ref": {

View File

@ -46,7 +46,7 @@
"@types/validator": "^13.6.3",
"@vitejs/plugin-react": "^1.0.0",
"rollup-plugin-visualizer": "^5.5.2",
"typescript": "^4.3.2",
"typescript": "^4.5.2",
"vite": "^2.6.4"
}
}

View File

@ -17,17 +17,9 @@ import CourseForm from './CourseForm';
/**
* Hooks
*/
import { useState } from 'react';
import { useHistory } from 'react-router';
import { useForm } from 'react-hook-form';
import { useToast } from '@chakra-ui/toast';
import useIsMountedRef from '@educt/hooks/useIsMountedRef';
import { useErrorHandler } from 'react-error-boundary';
/**
* Services
*/
import { CourseServiceInstance } from '@educt/services';
import { useCreateCourse } from '@educt/hooks/queries';
/**
* Schema
@ -37,21 +29,16 @@ import CourseFormSchema from './CourseForm.validator';
type CreateFormCoursePropsType = {};
const CreateFormCourse: React.FC<CreateFormCoursePropsType> = () => {
const [isLoading, setIsLoading] = useState<boolean>(false);
const form = useForm<InputFields>({ resolver: yupResolver(CourseFormSchema) });
const isMountedRef = useIsMountedRef();
const { createCourse, isLoading } = useCreateCourse();
const history = useHistory();
const toast = useToast();
const handleError = useErrorHandler();
/**
* Submit handler
*/
// TODO: move request to useCreateCourse hook
const onSubmit: SubmitHandler<InputFields> = async data => {
try {
setIsLoading(true);
const course = await CourseServiceInstance.create({
const course = await createCourse({
title: data.title,
description: data.description,
teacher_id: data.teacher_id,
@ -59,22 +46,10 @@ const CreateFormCourse: React.FC<CreateFormCoursePropsType> = () => {
image: data.image,
status: CourseStatusEnum.DRAFT,
});
toast({ title: `Course successfully created.`, status: 'success' });
history.push(`/courses/edit/${course.data.id}`);
} catch (error: any) {
if (error.response) {
if (error.response.status === 422) {
toast({ title: `${error.response.data.errors[0].message}`, status: 'error' });
} else {
toast({ title: `${error.message}`, status: 'error' });
}
} else {
handleError(error);
}
} finally {
if (isMountedRef.current) {
setIsLoading(false);
}
if (course) history.push(`/courses/edit/${course.id}`);
} catch (error) {
console.error(error);
}
};

View File

@ -16,21 +16,14 @@ import CourseForm from './CourseForm';
/**
* Hooks
*/
import { useState } from 'react';
import { useParams } from 'react-router';
import { useForm } from 'react-hook-form';
import { useErrorHandler } from 'react-error-boundary';
import { useToast } from '@chakra-ui/toast';
/**
* Services
*/
import { CourseServiceInstance } from '@educt/services';
/**
* Schema
*/
import CourseFormSchema from './CourseForm.validator';
import { useUpdateCourse } from '@educt/hooks/queries';
type EditFormCoursePropsType = {
defaultValues?:
@ -45,7 +38,6 @@ type EditFormCoursePropsType = {
};
const EditFormCourse: React.FC<EditFormCoursePropsType> = ({ defaultValues }) => {
const [isLoading, setIsLoading] = useState<boolean>(false);
const form = useForm<InputFields>({
resolver: yupResolver(CourseFormSchema),
defaultValues: {
@ -56,44 +48,30 @@ const EditFormCourse: React.FC<EditFormCoursePropsType> = ({ defaultValues }) =>
image: undefined,
},
});
const toast = useToast();
const handleError = useErrorHandler();
const { updateCourse, isLoading } = useUpdateCourse();
const { id } = useParams<{ id: string }>();
/**
* Submit handler
*/
// TODO move request to useUpdateCourse hook
const onSubmit: SubmitHandler<InputFields> = async data => {
try {
setIsLoading(true);
const course = await CourseServiceInstance.update(id, {
title: data.title,
description: data.description,
teacher_id: data.teacher_id,
category_id: data.category_id,
image: data.image,
});
toast({ title: `Course updated.`, status: 'info' });
const updated = await updateCourse(id, data);
/**
* Update fields with new values
*/
form.reset({
title: course.data.title,
description: course.data.description,
teacher_id: course.data.teacher.id,
category_id: course.data.category.id,
image: undefined,
});
} catch (error: any) {
if (error.response) {
toast({ title: `${error.message}`, status: 'error' });
} else {
handleError(error);
if (updated) {
/**
* Update fields with new values
*/
form.reset({
title: updated.title,
description: updated.description,
teacher_id: updated.teacher.id,
category_id: updated.category.id,
image: undefined,
});
}
} finally {
setIsLoading(false);
} catch (error) {
console.error(error);
}
};

View File

@ -0,0 +1,49 @@
import { useToast } from '@chakra-ui/toast';
import { CourseStatusEnum } from '@educt/enums';
import { ICourse } from '@educt/interfaces';
import { CourseServiceInstance } from '@educt/services';
import { CreateCourseParamsType } from '@educt/types';
import { useErrorHandler } from 'react-error-boundary';
import useAsync from '../../useAsync';
type CreatedCourseDataType =
| Pick<ICourse, 'id' | 'title' | 'description' | 'status' | 'created_at' | 'updated_at'>
| undefined;
const useCreateCourse = () => {
const toast = useToast();
const handleError = useErrorHandler();
const createCourse = async (data: CreateCourseParamsType) => {
try {
const result = await CourseServiceInstance.create({
title: data.title,
description: data.description,
teacher_id: data.teacher_id,
category_id: data.category_id,
image: data.image,
status: CourseStatusEnum.DRAFT,
});
toast({ title: `Course successfully created.`, status: 'success' });
return result.data;
} catch (error: any) {
if (error.response) {
if (error.response.status === 422) {
toast({ title: `${error.response.data.errors[0].message}`, status: 'error' });
} else {
toast({ title: `${error.message}`, status: 'error' });
}
} else {
handleError(error);
}
return Promise.reject(error);
}
};
const { execute, ...state } = useAsync<CreatedCourseDataType, Parameters<typeof createCourse>>(createCourse);
return { createCourse: execute, ...state };
};
export { useCreateCourse };

View File

@ -0,0 +1,35 @@
import { useToast } from '@chakra-ui/toast';
import { ICourse } from '@educt/interfaces';
import { useErrorHandler } from 'react-error-boundary';
import { useRootStore } from '@educt/hooks/useRootStore';
import useAsync from '@educt/hooks/useAsync';
type DeletedCourseDataType = Omit<ICourse, 'teacher' | 'students' | 'lessons'> | undefined;
const useDeleteCourse = () => {
const { courseStore } = useRootStore();
const toast = useToast();
const handleError = useErrorHandler();
const deleteCourse = async (id: string) => {
try {
const result = await courseStore.deleteCourse(id);
toast({ title: 'Course deleted.', status: 'info' });
return result.data;
} catch (error: any) {
if (error.response) {
toast({ title: error.message, status: 'error' });
} else {
handleError(error);
}
return Promise.reject(error);
}
};
const { execute, ...state } = useAsync<DeletedCourseDataType, Parameters<typeof deleteCourse>>(deleteCourse);
return { deleteCourse: execute, ...state };
};
export { useDeleteCourse };

View File

@ -0,0 +1,41 @@
import { useToast } from '@chakra-ui/toast';
import { useErrorHandler } from 'react-error-boundary';
import { CourseServiceInstance } from '@educt/services';
import { UpdateCourseParamsType } from '@educt/types';
import useAsync from '../../useAsync';
import { ICourse } from '@educt/interfaces';
type UpdatedCourseDataType = Omit<ICourse, 'students_count' | 'likes_count' | 'lessons_count'> | undefined;
const useUpdateCourse = () => {
const toast = useToast();
const handleError = useErrorHandler();
const updateCourse = async (id: string, data: UpdateCourseParamsType) => {
try {
const result = await CourseServiceInstance.update(id, {
title: data.title,
description: data.description,
teacher_id: data.teacher_id,
category_id: data.category_id,
image: data.image,
});
toast({ title: `Course updated.`, status: 'info' });
return result.data;
} catch (error: any) {
if (error.response) {
toast({ title: `${error.message}`, status: 'error' });
} else {
handleError(error);
}
return Promise.reject(error);
}
};
const { execute, ...state } = useAsync<UpdatedCourseDataType, Parameters<typeof updateCourse>>(updateCourse);
return { updateCourse: execute, ...state };
};
export { useUpdateCourse };

View File

@ -1,3 +1,6 @@
import { useDeleteLesson } from './useDeleteLesson';
import { useDeleteLesson } from './lesson/useDeleteLesson';
export { useDeleteLesson };
import { useCreateCourse } from './course/useCreateCourse';
import { useUpdateCourse } from './course/useUpdateCourse';
export { useCreateCourse, useUpdateCourse, useDeleteLesson };

View File

@ -1,8 +1,10 @@
import { useToast } from '@chakra-ui/toast';
import { ILesson } from '@educt/interfaces';
import { useErrorHandler } from 'react-error-boundary';
import useAsync from '../useAsync';
import { useRootStore } from '../useRootStore';
import useAsync from '../../useAsync';
import { useRootStore } from '../../useRootStore';
type CreatedLessonDataType = ILesson | undefined;
const useDeleteLesson = () => {
const {
@ -22,10 +24,12 @@ const useDeleteLesson = () => {
} else {
handleError(error);
}
return Promise.reject(error);
}
};
const { execute, ...state } = useAsync<ILesson | undefined, Parameters<typeof deleteLesson>>(deleteLesson);
const { execute, ...state } = useAsync<CreatedLessonDataType, Parameters<typeof deleteLesson>>(deleteLesson);
return { deleteLesson: execute, ...state };
};

View File

@ -1,14 +1,9 @@
import React, { useContext, useRef, useState } from 'react';
import {
AlertDialog,
AlertDialogBody,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogContent,
AlertDialogOverlay,
Button,
useToast,
} from '@chakra-ui/react';
import React, { useContext } from 'react';
/**
* Types
*/
import { ICourse } from '@educt/interfaces';
/**
* Components
@ -23,9 +18,7 @@ import { CoursesPageContext } from '@educt/contexts';
/**
* Hooks
*/
import { useErrorHandler } from 'react-error-boundary';
import { useRootStore } from '@educt/hooks/useRootStore';
import { ICourse } from '@educt/interfaces';
import { useDeleteCourse } from '@educt/hooks/queries/course/useDeleteCourse';
type DeleteCourseDialogPropsType = {
course: Pick<ICourse, 'id' | 'title'>;
@ -34,34 +27,14 @@ type DeleteCourseDialogPropsType = {
};
const DeleteCourseDialog: React.FC<DeleteCourseDialogPropsType> = ({ onClose, isOpen, course }) => {
const { courseStore } = useRootStore();
const { setDeletingCourse } = useContext(CoursesPageContext);
const [isLoading, setIsLoading] = useState<boolean>(false);
const handleError = useErrorHandler();
const toast = useToast();
const { deleteCourse, isLoading } = useDeleteCourse();
/**
* Delete course handler
*
* @param isConfirmed Is dialog confirmed
* @param id Course id
*/
const deleteCourseHandler = async (isConfirmed: boolean, id: string) => {
const handleDelete = async (isConfirmed: boolean, id: string) => {
if (isConfirmed) {
try {
setIsLoading(true);
await courseStore.deleteCourse(id);
toast({ title: 'Course deleted.', status: 'info' });
} catch (error: any) {
if (error.response) {
toast({ title: error.message, status: 'error' });
} else {
handleError(error);
}
} finally {
setIsLoading(false);
setDeletingCourse(undefined);
}
await deleteCourse(id)
.catch(error => console.error(error))
.finally(() => setDeletingCourse(undefined));
}
/**
@ -74,7 +47,7 @@ const DeleteCourseDialog: React.FC<DeleteCourseDialogPropsType> = ({ onClose, is
<ConfirmDialog
isOpen={isOpen}
isLoading={isLoading}
onClose={isConfirmed => deleteCourseHandler(isConfirmed, course.id)}
onClose={isConfirmed => handleDelete(isConfirmed, course.id)}
title='Confirm deleting'
message={`The course with title "${course.title}" will be removed from the system. After deleting a course, the lessons in it will also be deleted.`}
confirmMessage='Delete'

View File

@ -38,7 +38,7 @@ type UpdateUserContactsFormPropsType = {
const UpdateUserContactsForm: React.FC<UpdateUserContactsFormPropsType> = ({ contacts }) => {
const {
register,
formState: { errors, isDirty },
formState: { errors, dirtyFields, isDirty },
handleSubmit,
reset,
} = useForm<UpdateUserContactsInputType>({