refactor: user query hooks

This commit is contained in:
Sergey Yarkov 2021-12-02 22:50:06 +03:00
parent e329326821
commit 321149a1f2
21 changed files with 189 additions and 107 deletions

View File

@ -47,7 +47,7 @@ const CreateFormCourse: React.FC<CreateFormCoursePropsType> = () => {
status: CourseStatusEnum.DRAFT, status: CourseStatusEnum.DRAFT,
}); });
if (course) history.push(`/courses/edit/${course.id}`); history.push(`/courses/edit/${course.id}`);
} catch (error) { } catch (error) {
console.error(error); console.error(error);
} }

View File

@ -58,7 +58,6 @@ const EditFormCourse: React.FC<EditFormCoursePropsType> = ({ defaultValues }) =>
try { try {
const updated = await updateCourse(id, data); const updated = await updateCourse(id, data);
if (updated) {
/** /**
* Update fields with new values * Update fields with new values
*/ */
@ -69,7 +68,6 @@ const EditFormCourse: React.FC<EditFormCoursePropsType> = ({ defaultValues }) =>
category_id: updated.category.id, category_id: updated.category.id,
image: undefined, image: undefined,
}); });
}
} catch (error) { } catch (error) {
console.error(error); console.error(error);
} }

View File

@ -84,3 +84,7 @@ export function arrayMove<T>(array: T[], from: number, to: number) {
result.splice(to, 0, removed); result.splice(to, 0, removed);
return result; return result;
} }
export function getDirtyFields<T>(dirtyFields: { [key: string]: boolean | undefined }, data: T) {
return Object.fromEntries(Object.keys(dirtyFields).map(k => [k, data[k as keyof T]]));
}

View File

@ -4,7 +4,7 @@ import { useErrorHandler } from 'react-error-boundary';
import useAsync from '../../useAsync'; import useAsync from '../../useAsync';
import { useRootStore } from '../../useRootStore'; import { useRootStore } from '../../useRootStore';
type LoginResultDataType = IToken | undefined; type LoginResultDataType = IToken;
const useLogin = () => { const useLogin = () => {
const { authStore } = useRootStore(); const { authStore } = useRootStore();

View File

@ -6,9 +6,7 @@ import { CreateCourseParamsType } from '@educt/types';
import { useErrorHandler } from 'react-error-boundary'; import { useErrorHandler } from 'react-error-boundary';
import useAsync from '../../useAsync'; import useAsync from '../../useAsync';
type CreatedCourseDataType = type CreatedCourseDataType = Pick<ICourse, 'id' | 'title' | 'description' | 'status' | 'created_at' | 'updated_at'>;
| Pick<ICourse, 'id' | 'title' | 'description' | 'status' | 'created_at' | 'updated_at'>
| undefined;
const useCreateCourse = () => { const useCreateCourse = () => {
const toast = useToast(); const toast = useToast();

View File

@ -4,7 +4,7 @@ import { useErrorHandler } from 'react-error-boundary';
import { useRootStore } from '@educt/hooks/useRootStore'; import { useRootStore } from '@educt/hooks/useRootStore';
import useAsync from '@educt/hooks/useAsync'; import useAsync from '@educt/hooks/useAsync';
type DeletedCourseDataType = Omit<ICourse, 'teacher' | 'students' | 'lessons'> | undefined; type DeletedCourseDataType = Omit<ICourse, 'teacher' | 'students' | 'lessons'>;
const useDeleteCourse = () => { const useDeleteCourse = () => {
const { courseStore } = useRootStore(); const { courseStore } = useRootStore();

View File

@ -10,7 +10,7 @@ import { useEffect } from 'react';
import useAsync from '../../useAsync'; import useAsync from '../../useAsync';
import { useRootStore } from '../../useRootStore'; import { useRootStore } from '../../useRootStore';
type CourseResultDataType = Omit<ICourse, 'students_count' | 'likes_count' | 'lessons_count'> | undefined; type CourseResultDataType = Omit<ICourse, 'students_count' | 'likes_count' | 'lessons_count'>;
const useFetchCourse = (id: string) => { const useFetchCourse = (id: string) => {
const { const {

View File

@ -7,11 +7,8 @@ import useAsync from '../../useAsync';
import { useRootStore } from '../../useRootStore'; import { useRootStore } from '../../useRootStore';
import { useToast } from '@chakra-ui/toast'; import { useToast } from '@chakra-ui/toast';
type SetCourseStatusResultDataType = {} | undefined; type SetCourseStatusResultDataType = {};
export type SetCourseStatusFnType = ( export type SetCourseStatusFnType = (id: string, newStatus: CourseStatusEnum) => Promise<SetCourseStatusResultDataType>;
id: string,
newStatus: CourseStatusEnum
) => Promise<SetCourseStatusResultDataType | undefined>;
const useSetCourseStatus = () => { const useSetCourseStatus = () => {
const { courseStore } = useRootStore(); const { courseStore } = useRootStore();

View File

@ -5,7 +5,7 @@ import { UpdateCourseParamsType } from '@educt/types';
import useAsync from '../../useAsync'; import useAsync from '../../useAsync';
import { ICourse } from '@educt/interfaces'; import { ICourse } from '@educt/interfaces';
type UpdatedCourseDataType = Omit<ICourse, 'students_count' | 'likes_count' | 'lessons_count'> | undefined; type UpdatedCourseDataType = Omit<ICourse, 'students_count' | 'likes_count' | 'lessons_count'>;
const useUpdateCourse = () => { const useUpdateCourse = () => {
const toast = useToast(); const toast = useToast();

View File

@ -20,6 +20,9 @@ import { useSetCourseStatus } from './course/useSetCourseStatus';
/** /**
* User * User
*/ */
import { useCreateUser } from './user/useCreateUser';
import { useDeleteUser } from './user/useDeleteUser';
import { useUpdateUser } from './user/useUpdateUser';
import { useUpdateUserEmail } from './user/useUpdateUserEmail'; import { useUpdateUserEmail } from './user/useUpdateUserEmail';
export { export {
@ -30,5 +33,8 @@ export {
useFetchCourse, useFetchCourse,
useSetCourseStatus, useSetCourseStatus,
useDeleteLesson, useDeleteLesson,
useCreateUser,
useDeleteUser,
useUpdateUser,
useUpdateUserEmail, useUpdateUserEmail,
}; };

View File

@ -4,7 +4,7 @@ import { useErrorHandler } from 'react-error-boundary';
import useAsync from '../../useAsync'; import useAsync from '../../useAsync';
import { useRootStore } from '../../useRootStore'; import { useRootStore } from '../../useRootStore';
type CreatedLessonDataType = ILesson | undefined; type CreatedLessonDataType = ILesson;
const useDeleteLesson = () => { const useDeleteLesson = () => {
const { const {

View File

@ -0,0 +1,43 @@
import { useErrorHandler } from 'react-error-boundary';
import { CreateUserParamsType, FetchUsersParamsType } from '@educt/types';
import { useRootStore } from '@educt/hooks/useRootStore';
import { useToast } from '@chakra-ui/toast';
import { IUser } from '@educt/interfaces';
import useAsync from '@educt/hooks/useAsync';
type CreatedUserDataType = IUser;
const useCreateUser = () => {
const { userStore } = useRootStore();
const toast = useToast();
const handleError = useErrorHandler();
const createUser = async (data: CreateUserParamsType, paramsContext?: FetchUsersParamsType | undefined) => {
try {
const result = await userStore.createUser(data, paramsContext);
toast({ title: 'User created.', status: 'info' });
return result.data;
} catch (error: any) {
if (error.response) {
switch (error.response.status) {
case 422:
toast({ title: `${error.response.data.errors[0].message}`, status: 'error' });
break;
default:
toast({ title: `${error.message}`, status: 'error' });
break;
}
} else {
handleError(error);
}
return Promise.reject(error);
}
};
const { execute, ...state } = useAsync<CreatedUserDataType, Parameters<typeof createUser>>(createUser);
return { createUser: execute, ...state };
};
export { useCreateUser };

View File

@ -0,0 +1,36 @@
import { useToast } from '@chakra-ui/toast';
import useAsync from '@educt/hooks/useAsync';
import { useRootStore } from '@educt/hooks/useRootStore';
import { IUser } from '@educt/interfaces';
import { FetchUsersParamsType } from '@educt/types';
import { useErrorHandler } from 'react-error-boundary';
type DeletedUserDataType = IUser;
const useDeleteUser = () => {
const { userStore } = useRootStore();
const toast = useToast();
const handleError = useErrorHandler();
const deleteUser = async (id: string, paramsContext?: FetchUsersParamsType | undefined) => {
try {
const result = await userStore.deleteUser(id, paramsContext);
toast({ title: 'User 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<DeletedUserDataType, Parameters<typeof deleteUser>>(deleteUser);
return { deleteUser: execute, ...state };
};
export { useDeleteUser };

View File

@ -0,0 +1,43 @@
import { useErrorHandler } from 'react-error-boundary';
import { useToast } from '@chakra-ui/toast';
import { useRootStore } from '@educt/hooks/useRootStore';
import { UpdateUserParamsType } from '@educt/types';
import { IUser } from '@educt/interfaces';
import useAsync from '@educt/hooks/useAsync';
type UpdatedUserDataType = IUser;
const useUpdateUser = () => {
const { userStore } = useRootStore();
const toast = useToast();
const handleError = useErrorHandler();
const updateUser = async (id: string, params: UpdateUserParamsType) => {
try {
const result = await userStore.updateUser(id, params);
toast({ title: 'User updated.', status: 'info' });
return result.data;
} catch (error: any) {
if (error.response) {
switch (error.response.status) {
case 422:
toast({ title: `${error.response.data.errors[0].message}`, status: 'error' });
break;
default:
toast({ title: `${error.message}`, status: 'error' });
break;
}
} else {
handleError(error);
}
return Promise.reject(error);
}
};
const { execute, ...state } = useAsync<UpdatedUserDataType, Parameters<typeof updateUser>>(updateUser);
return { updateUser: execute, ...state };
};
export { useUpdateUser };

View File

@ -3,7 +3,7 @@ import { useErrorHandler } from 'react-error-boundary';
import { useRootStore } from '@educt/hooks/useRootStore'; import { useRootStore } from '@educt/hooks/useRootStore';
import useAsync from '@educt/hooks/useAsync'; import useAsync from '@educt/hooks/useAsync';
type ConfirmationResultDataType = { expired_seconds: number } | null; type ConfirmationResultDataType = { expired_seconds: number };
const useUpdateUserEmail = () => { const useUpdateUserEmail = () => {
const { userStore } = useRootStore(); const { userStore } = useRootStore();

View File

@ -32,9 +32,13 @@ const DeleteCourseDialog: React.FC<DeleteCourseDialogPropsType> = ({ onClose, is
const handleDelete = async (isConfirmed: boolean, id: string) => { const handleDelete = async (isConfirmed: boolean, id: string) => {
if (isConfirmed) { if (isConfirmed) {
await deleteCourse(id) try {
.catch(error => console.error(error)) await deleteCourse(id);
.finally(() => setDeletingCourse(undefined)); } catch (error: any) {
console.error(error);
} finally {
setDeletingCourse(undefined);
}
} }
/** /**

View File

@ -93,13 +93,12 @@ const ConfirmEmailContainer: React.FC<ConfirmEmailContainerPropsType> = ({ data
const resendCodeHandler = async () => { const resendCodeHandler = async () => {
const result = await sendConfirmationCode(data.newEmail); const result = await sendConfirmationCode(data.newEmail);
if (result !== null) {
/** /**
* Reset timer state * Reset timer state
*/ */
const { expired_seconds } = result; const { expired_seconds } = result;
setExpired(v => (v = expired_seconds)); setExpired(v => (v = expired_seconds));
}
}; };
return ( return (

View File

@ -31,13 +31,15 @@ import { SubmitHandler } from 'react-hook-form';
/** /**
* Hooks * Hooks
*/ */
import { useContext, useState } from 'react'; import { useContext } from 'react';
import useIsMountedRef from '@educt/hooks/useIsMountedRef';
import { useDisclosure } from '@chakra-ui/hooks'; import { useDisclosure } from '@chakra-ui/hooks';
import { useForm } from 'react-hook-form'; import { useForm } from 'react-hook-form';
import { useRootStore } from '@educt/hooks/useRootStore'; import { useRootStore } from '@educt/hooks/useRootStore';
import { useErrorHandler } from 'react-error-boundary';
import { useToast } from '@chakra-ui/react'; /**
* Hooks
*/
import { useCreateUser } from '@educt/hooks/queries';
/** /**
* Contexts * Contexts
@ -63,6 +65,7 @@ type CreateUserInputType = {
const CreateUserModal: React.FC<CreateUserFormPropsType> = () => { const CreateUserModal: React.FC<CreateUserFormPropsType> = () => {
const { userStore } = useRootStore(); const { userStore } = useRootStore();
const { searchingRole, search } = useContext(UsersPageContext); const { searchingRole, search } = useContext(UsersPageContext);
const { createUser, isLoading } = useCreateUser();
const { isOpen, onOpen, onClose } = useDisclosure(); const { isOpen, onOpen, onClose } = useDisclosure();
const { const {
register, register,
@ -72,10 +75,6 @@ const CreateUserModal: React.FC<CreateUserFormPropsType> = () => {
} = useForm<CreateUserInputType>({ } = useForm<CreateUserInputType>({
resolver: yupResolver(CreateUserSchema), resolver: yupResolver(CreateUserSchema),
}); });
const [loading, setLoading] = useState<boolean>(false);
const isMountedRef = useIsMountedRef();
const toast = useToast();
const handleError = useErrorHandler();
const { me } = userStore; const { me } = userStore;
@ -84,17 +83,14 @@ const CreateUserModal: React.FC<CreateUserFormPropsType> = () => {
/** /**
* Submit handler * Submit handler
*/ */
// TODO: move to hook
const onSubmit: SubmitHandler<CreateUserInputType> = async data => { const onSubmit: SubmitHandler<CreateUserInputType> = async data => {
try { try {
setLoading(true); await createUser(data, {
await userStore.createUser(data, {
page: userStore.pagination?.current_page, page: userStore.pagination?.current_page,
limit: userStore.pagination?.per_page, limit: userStore.pagination?.per_page,
role: searchingRole, role: searchingRole,
search, search,
}); });
toast({ title: 'User created.', status: 'info' });
/** /**
* Clear form state * Clear form state
@ -106,19 +102,7 @@ const CreateUserModal: React.FC<CreateUserFormPropsType> = () => {
*/ */
onClose(); onClose();
} catch (error: any) { } catch (error: any) {
if (error.response) { console.error(error);
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) {
setLoading(false);
}
} }
}; };
@ -195,7 +179,7 @@ const CreateUserModal: React.FC<CreateUserFormPropsType> = () => {
variant='outline' variant='outline'
mr={3} mr={3}
colorScheme='blue' colorScheme='blue'
isLoading={loading} isLoading={isLoading}
loadingText='Creating...' loadingText='Creating...'
leftIcon={<AddIcon />} leftIcon={<AddIcon />}
> >

View File

@ -18,10 +18,9 @@ import { UsersPageContext } from '@educt/contexts';
/** /**
* Hooks * Hooks
*/ */
import { useContext, useState } from 'react'; import { useContext } from 'react';
import { useRootStore } from '@educt/hooks/useRootStore'; import { useRootStore } from '@educt/hooks/useRootStore';
import { useToast } from '@chakra-ui/react'; import { useDeleteUser } from '@educt/hooks/queries';
import { useErrorHandler } from 'react-error-boundary';
type DeleteUserDialogPropsType = { type DeleteUserDialogPropsType = {
user: IUser; user: IUser;
@ -32,9 +31,7 @@ type DeleteUserDialogPropsType = {
const DeleteUserDialog: React.FC<DeleteUserDialogPropsType> = ({ isOpen, onClose, user }) => { const DeleteUserDialog: React.FC<DeleteUserDialogPropsType> = ({ isOpen, onClose, user }) => {
const { userStore } = useRootStore(); const { userStore } = useRootStore();
const { setDeletingUser, searchingRole, searchingPage, search } = useContext(UsersPageContext); const { setDeletingUser, searchingRole, searchingPage, search } = useContext(UsersPageContext);
const [isLoading, setIsLoading] = useState<boolean>(false); const { deleteUser, isLoading } = useDeleteUser();
const handleError = useErrorHandler();
const toast = useToast();
const { pagination } = userStore; const { pagination } = userStore;
@ -44,26 +41,18 @@ const DeleteUserDialog: React.FC<DeleteUserDialogPropsType> = ({ isOpen, onClose
* @param isConfirmed Is dialog confirmed * @param isConfirmed Is dialog confirmed
* @param id User id * @param id User id
*/ */
// TODO: move to hook
const deleteUserHandler = async (isConfirmed: boolean, id: string) => { const deleteUserHandler = async (isConfirmed: boolean, id: string) => {
if (isConfirmed) { if (isConfirmed) {
try { try {
setIsLoading(true); await deleteUser(id, {
await userStore.deleteUser(id, {
page: searchingPage, page: searchingPage,
limit: pagination?.per_page, limit: pagination?.per_page,
role: searchingRole, role: searchingRole,
search, search,
}); });
toast({ title: 'User deleted.', status: 'info' });
} catch (error: any) { } catch (error: any) {
if (error.response) { console.error(error);
toast({ title: error.message, status: 'error' });
} else {
handleError(error);
}
} finally { } finally {
setIsLoading(false);
setDeletingUser(undefined); setDeletingUser(undefined);
} }
} }

View File

@ -1,4 +1,5 @@
import React, { useContext, useState } from 'react'; import React, { useContext } from 'react';
import * as helpers from '@educt/helpers';
import { import {
Modal, Modal,
ModalOverlay, ModalOverlay,
@ -25,7 +26,7 @@ import { yupResolver } from '@hookform/resolvers/yup';
/** /**
* Types * Types
*/ */
import { IMe, IUser } from '@educt/interfaces'; import { IUser } from '@educt/interfaces';
import { UserRoleEnum } from '@educt/enums'; import { UserRoleEnum } from '@educt/enums';
import { SubmitHandler } from 'react-hook-form'; import { SubmitHandler } from 'react-hook-form';
@ -34,9 +35,6 @@ import { SubmitHandler } from 'react-hook-form';
*/ */
import { useForm } from 'react-hook-form'; import { useForm } from 'react-hook-form';
import { useRootStore } from '@educt/hooks/useRootStore'; import { useRootStore } from '@educt/hooks/useRootStore';
import { useErrorHandler } from 'react-error-boundary';
import { useToast } from '@chakra-ui/react';
import useIsMountedRef from '@educt/hooks/useIsMountedRef';
/** /**
* Contexts * Contexts
@ -47,6 +45,7 @@ import { UsersPageContext } from '@educt/contexts';
* Schema * Schema
*/ */
import UpdateUserSchema from './EditUserForm.validator'; import UpdateUserSchema from './EditUserForm.validator';
import { useUpdateUser } from '@educt/hooks/queries';
type UpdateUserFormPropsType = { type UpdateUserFormPropsType = {
user: IUser; user: IUser;
@ -66,7 +65,7 @@ type UpdateUserInputType = {
const UpdateUserForm: React.FC<UpdateUserFormPropsType> = ({ user, onClose, isOpen }) => { const UpdateUserForm: React.FC<UpdateUserFormPropsType> = ({ user, onClose, isOpen }) => {
const { userStore } = useRootStore(); const { userStore } = useRootStore();
const { setEditingUser } = useContext(UsersPageContext); const { setEditingUser } = useContext(UsersPageContext);
const [isLoading, setIsLoading] = useState<boolean>(false); const { updateUser, isLoading } = useUpdateUser();
const { const {
register, register,
handleSubmit, handleSubmit,
@ -83,9 +82,6 @@ const UpdateUserForm: React.FC<UpdateUserFormPropsType> = ({ user, onClose, isOp
password: '', password: '',
}, },
}); });
const isMountedRef = useIsMountedRef();
const toast = useToast();
const handleError = useErrorHandler();
const { me } = userStore; const { me } = userStore;
@ -94,28 +90,13 @@ const UpdateUserForm: React.FC<UpdateUserFormPropsType> = ({ user, onClose, isOp
/** /**
* Submit handler * Submit handler
*/ */
// TODO: move to hook
const onSubmit: SubmitHandler<UpdateUserInputType> = async data => { const onSubmit: SubmitHandler<UpdateUserInputType> = async data => {
try { try {
setIsLoading(true); const params = helpers.getDirtyFields<UpdateUserInputType>(dirtyFields, data);
const params = Object.fromEntries(Object.keys(dirtyFields).map(k => [k, data[k as keyof UpdateUserInputType]])); await updateUser(user.id, params);
await userStore.updateUser(user.id, params);
toast({ title: 'User updated.', status: 'info' });
onCloseModal(); onCloseModal();
} catch (error: any) { } catch (error: any) {
if (error.response) { console.error(error);
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);
}
} }
}; };

View File

@ -8,7 +8,7 @@ import { CourseServiceInstance, LessonServiceInstance } from '@educt/services';
export default class EditCourseStore { export default class EditCourseStore {
public pageStore: PageStore; public pageStore: PageStore;
public course: Omit<ICourse, 'students_count' | 'likes_count' | 'lessons_count'> | undefined; public course: Omit<ICourse, 'students_count' | 'likes_count' | 'lessons_count'> | null = null;
public deletingLesson: ILesson | undefined; public deletingLesson: ILesson | undefined;