From dd86228cabf90da4534aa414274aa72b3c401c54 Mon Sep 17 00:00:00 2001 From: Sergey Yarkov Date: Wed, 1 Dec 2021 23:58:30 +0300 Subject: [PATCH] refactor: refactored course & lesson query hooks --- package-lock.json | 6 +-- package.json | 2 +- .../Forms/CourseForm/CreateCourseForm.tsx | 39 +++----------- .../Forms/CourseForm/EditCourseForm.tsx | 54 ++++++------------- src/hooks/queries/course/useCreateCourse.ts | 49 +++++++++++++++++ src/hooks/queries/course/useDeleteCourse.ts | 35 ++++++++++++ src/hooks/queries/course/useUpdateCourse.ts | 41 ++++++++++++++ src/hooks/queries/index.ts | 7 ++- .../queries/{ => lesson}/useDeleteLesson.ts | 10 ++-- .../DeleteCourseDialog/DeleteCourseDialog.tsx | 53 +++++------------- .../UpdateUserContactsForm.tsx | 2 +- 11 files changed, 178 insertions(+), 120 deletions(-) create mode 100644 src/hooks/queries/course/useCreateCourse.ts create mode 100644 src/hooks/queries/course/useDeleteCourse.ts create mode 100644 src/hooks/queries/course/useUpdateCourse.ts rename src/hooks/queries/{ => lesson}/useDeleteLesson.ts (72%) diff --git a/package-lock.json b/package-lock.json index 41e8227..5038596 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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": { diff --git a/package.json b/package.json index a7d06b4..95776c8 100644 --- a/package.json +++ b/package.json @@ -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" } } diff --git a/src/components/Forms/CourseForm/CreateCourseForm.tsx b/src/components/Forms/CourseForm/CreateCourseForm.tsx index b2350d8..dbf4fd6 100644 --- a/src/components/Forms/CourseForm/CreateCourseForm.tsx +++ b/src/components/Forms/CourseForm/CreateCourseForm.tsx @@ -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 = () => { - const [isLoading, setIsLoading] = useState(false); const form = useForm({ 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 = 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 = () => { 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); } }; diff --git a/src/components/Forms/CourseForm/EditCourseForm.tsx b/src/components/Forms/CourseForm/EditCourseForm.tsx index 063872c..bff3227 100644 --- a/src/components/Forms/CourseForm/EditCourseForm.tsx +++ b/src/components/Forms/CourseForm/EditCourseForm.tsx @@ -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 = ({ defaultValues }) => { - const [isLoading, setIsLoading] = useState(false); const form = useForm({ resolver: yupResolver(CourseFormSchema), defaultValues: { @@ -56,44 +48,30 @@ const EditFormCourse: React.FC = ({ 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 = 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); } }; diff --git a/src/hooks/queries/course/useCreateCourse.ts b/src/hooks/queries/course/useCreateCourse.ts new file mode 100644 index 0000000..ab81821 --- /dev/null +++ b/src/hooks/queries/course/useCreateCourse.ts @@ -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 + | 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>(createCourse); + + return { createCourse: execute, ...state }; +}; + +export { useCreateCourse }; diff --git a/src/hooks/queries/course/useDeleteCourse.ts b/src/hooks/queries/course/useDeleteCourse.ts new file mode 100644 index 0000000..6f5553f --- /dev/null +++ b/src/hooks/queries/course/useDeleteCourse.ts @@ -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 | 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>(deleteCourse); + + return { deleteCourse: execute, ...state }; +}; + +export { useDeleteCourse }; diff --git a/src/hooks/queries/course/useUpdateCourse.ts b/src/hooks/queries/course/useUpdateCourse.ts new file mode 100644 index 0000000..98e623d --- /dev/null +++ b/src/hooks/queries/course/useUpdateCourse.ts @@ -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 | 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>(updateCourse); + + return { updateCourse: execute, ...state }; +}; + +export { useUpdateCourse }; diff --git a/src/hooks/queries/index.ts b/src/hooks/queries/index.ts index 2d0b14d..6ee26aa 100644 --- a/src/hooks/queries/index.ts +++ b/src/hooks/queries/index.ts @@ -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 }; diff --git a/src/hooks/queries/useDeleteLesson.ts b/src/hooks/queries/lesson/useDeleteLesson.ts similarity index 72% rename from src/hooks/queries/useDeleteLesson.ts rename to src/hooks/queries/lesson/useDeleteLesson.ts index d06837a..74fc45c 100644 --- a/src/hooks/queries/useDeleteLesson.ts +++ b/src/hooks/queries/lesson/useDeleteLesson.ts @@ -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>(deleteLesson); + const { execute, ...state } = useAsync>(deleteLesson); return { deleteLesson: execute, ...state }; }; diff --git a/src/pages/courses/components/DeleteCourseDialog/DeleteCourseDialog.tsx b/src/pages/courses/components/DeleteCourseDialog/DeleteCourseDialog.tsx index d59ff3a..1f152b7 100644 --- a/src/pages/courses/components/DeleteCourseDialog/DeleteCourseDialog.tsx +++ b/src/pages/courses/components/DeleteCourseDialog/DeleteCourseDialog.tsx @@ -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; @@ -34,34 +27,14 @@ type DeleteCourseDialogPropsType = { }; const DeleteCourseDialog: React.FC = ({ onClose, isOpen, course }) => { - const { courseStore } = useRootStore(); const { setDeletingCourse } = useContext(CoursesPageContext); - const [isLoading, setIsLoading] = useState(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 = ({ onClose, is 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' diff --git a/src/pages/profile/components/UpdateUserContactsForm/UpdateUserContactsForm.tsx b/src/pages/profile/components/UpdateUserContactsForm/UpdateUserContactsForm.tsx index ff9c601..933df5d 100644 --- a/src/pages/profile/components/UpdateUserContactsForm/UpdateUserContactsForm.tsx +++ b/src/pages/profile/components/UpdateUserContactsForm/UpdateUserContactsForm.tsx @@ -38,7 +38,7 @@ type UpdateUserContactsFormPropsType = { const UpdateUserContactsForm: React.FC = ({ contacts }) => { const { register, - formState: { errors, isDirty }, + formState: { errors, dirtyFields, isDirty }, handleSubmit, reset, } = useForm({