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,
});
if (course) history.push(`/courses/edit/${course.id}`);
history.push(`/courses/edit/${course.id}`);
} catch (error) {
console.error(error);
}

View File

@ -58,18 +58,16 @@ const EditFormCourse: React.FC<EditFormCoursePropsType> = ({ defaultValues }) =>
try {
const updated = await updateCourse(id, data);
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,
});
}
/**
* 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,
});
} catch (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);
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 { useRootStore } from '../../useRootStore';
type LoginResultDataType = IToken | undefined;
type LoginResultDataType = IToken;
const useLogin = () => {
const { authStore } = useRootStore();

View File

@ -6,9 +6,7 @@ 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;
type CreatedCourseDataType = Pick<ICourse, 'id' | 'title' | 'description' | 'status' | 'created_at' | 'updated_at'>;
const useCreateCourse = () => {
const toast = useToast();

View File

@ -4,7 +4,7 @@ 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;
type DeletedCourseDataType = Omit<ICourse, 'teacher' | 'students' | 'lessons'>;
const useDeleteCourse = () => {
const { courseStore } = useRootStore();

View File

@ -10,7 +10,7 @@ import { useEffect } from 'react';
import useAsync from '../../useAsync';
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 {

View File

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

View File

@ -5,7 +5,7 @@ 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;
type UpdatedCourseDataType = Omit<ICourse, 'students_count' | 'likes_count' | 'lessons_count'>;
const useUpdateCourse = () => {
const toast = useToast();

View File

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

View File

@ -4,7 +4,7 @@ import { useErrorHandler } from 'react-error-boundary';
import useAsync from '../../useAsync';
import { useRootStore } from '../../useRootStore';
type CreatedLessonDataType = ILesson | undefined;
type CreatedLessonDataType = ILesson;
const useDeleteLesson = () => {
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 useAsync from '@educt/hooks/useAsync';
type ConfirmationResultDataType = { expired_seconds: number } | null;
type ConfirmationResultDataType = { expired_seconds: number };
const useUpdateUserEmail = () => {
const { userStore } = useRootStore();

View File

@ -32,9 +32,13 @@ const DeleteCourseDialog: React.FC<DeleteCourseDialogPropsType> = ({ onClose, is
const handleDelete = async (isConfirmed: boolean, id: string) => {
if (isConfirmed) {
await deleteCourse(id)
.catch(error => console.error(error))
.finally(() => setDeletingCourse(undefined));
try {
await deleteCourse(id);
} 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 result = await sendConfirmationCode(data.newEmail);
if (result !== null) {
/**
* Reset timer state
*/
const { expired_seconds } = result;
setExpired(v => (v = expired_seconds));
}
/**
* Reset timer state
*/
const { expired_seconds } = result;
setExpired(v => (v = expired_seconds));
};
return (

View File

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

View File

@ -18,10 +18,9 @@ import { UsersPageContext } from '@educt/contexts';
/**
* Hooks
*/
import { useContext, useState } from 'react';
import { useContext } from 'react';
import { useRootStore } from '@educt/hooks/useRootStore';
import { useToast } from '@chakra-ui/react';
import { useErrorHandler } from 'react-error-boundary';
import { useDeleteUser } from '@educt/hooks/queries';
type DeleteUserDialogPropsType = {
user: IUser;
@ -32,9 +31,7 @@ type DeleteUserDialogPropsType = {
const DeleteUserDialog: React.FC<DeleteUserDialogPropsType> = ({ isOpen, onClose, user }) => {
const { userStore } = useRootStore();
const { setDeletingUser, searchingRole, searchingPage, search } = useContext(UsersPageContext);
const [isLoading, setIsLoading] = useState<boolean>(false);
const handleError = useErrorHandler();
const toast = useToast();
const { deleteUser, isLoading } = useDeleteUser();
const { pagination } = userStore;
@ -44,26 +41,18 @@ const DeleteUserDialog: React.FC<DeleteUserDialogPropsType> = ({ isOpen, onClose
* @param isConfirmed Is dialog confirmed
* @param id User id
*/
// TODO: move to hook
const deleteUserHandler = async (isConfirmed: boolean, id: string) => {
if (isConfirmed) {
try {
setIsLoading(true);
await userStore.deleteUser(id, {
await deleteUser(id, {
page: searchingPage,
limit: pagination?.per_page,
role: searchingRole,
search,
});
toast({ title: 'User deleted.', status: 'info' });
} catch (error: any) {
if (error.response) {
toast({ title: error.message, status: 'error' });
} else {
handleError(error);
}
console.error(error);
} finally {
setIsLoading(false);
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 {
Modal,
ModalOverlay,
@ -25,7 +26,7 @@ import { yupResolver } from '@hookform/resolvers/yup';
/**
* Types
*/
import { IMe, IUser } from '@educt/interfaces';
import { IUser } from '@educt/interfaces';
import { UserRoleEnum } from '@educt/enums';
import { SubmitHandler } from 'react-hook-form';
@ -34,9 +35,6 @@ import { SubmitHandler } from 'react-hook-form';
*/
import { useForm } from 'react-hook-form';
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
@ -47,6 +45,7 @@ import { UsersPageContext } from '@educt/contexts';
* Schema
*/
import UpdateUserSchema from './EditUserForm.validator';
import { useUpdateUser } from '@educt/hooks/queries';
type UpdateUserFormPropsType = {
user: IUser;
@ -66,7 +65,7 @@ type UpdateUserInputType = {
const UpdateUserForm: React.FC<UpdateUserFormPropsType> = ({ user, onClose, isOpen }) => {
const { userStore } = useRootStore();
const { setEditingUser } = useContext(UsersPageContext);
const [isLoading, setIsLoading] = useState<boolean>(false);
const { updateUser, isLoading } = useUpdateUser();
const {
register,
handleSubmit,
@ -83,9 +82,6 @@ const UpdateUserForm: React.FC<UpdateUserFormPropsType> = ({ user, onClose, isOp
password: '',
},
});
const isMountedRef = useIsMountedRef();
const toast = useToast();
const handleError = useErrorHandler();
const { me } = userStore;
@ -94,28 +90,13 @@ const UpdateUserForm: React.FC<UpdateUserFormPropsType> = ({ user, onClose, isOp
/**
* Submit handler
*/
// TODO: move to hook
const onSubmit: SubmitHandler<UpdateUserInputType> = async data => {
try {
setIsLoading(true);
const params = Object.fromEntries(Object.keys(dirtyFields).map(k => [k, data[k as keyof UpdateUserInputType]]));
await userStore.updateUser(user.id, params);
toast({ title: 'User updated.', status: 'info' });
const params = helpers.getDirtyFields<UpdateUserInputType>(dirtyFields, data);
await updateUser(user.id, params);
onCloseModal();
} 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);
}
console.error(error);
}
};

View File

@ -8,7 +8,7 @@ import { CourseServiceInstance, LessonServiceInstance } from '@educt/services';
export default class EditCourseStore {
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;