feat: added useAsync hook

This commit is contained in:
Sergey Yarkov 2021-11-20 21:45:32 +03:00
parent 80ddcead4f
commit bf55565eed
6 changed files with 55 additions and 35 deletions

41
src/hooks/useAsync.ts Normal file
View File

@ -0,0 +1,41 @@
import { useCallback, useEffect, useState } from 'react';
import useIsMountedRef from './useIsMountedRef';
type StateType<DataResultT> = {
status: 'idle' | 'pending' | 'success' | 'error';
error: any;
data: DataResultT | null;
isLoading: boolean;
};
const useAsync = <T, P extends unknown[]>(callback: (...params: P) => Promise<T>) => {
const [state, setState] = useState<StateType<T>>({ status: 'idle', error: null, data: null, isLoading: false });
const isMountedRef = useIsMountedRef();
const execute = useCallback(
async (...params: P) => {
try {
setState(s => ({ ...s, isLoading: true, status: 'pending', data: null, error: null }));
const res = await callback(...params);
if (isMountedRef.current) {
setState(s => ({ ...s, status: 'success', data: res }));
}
return res;
} catch (error: any) {
if (isMountedRef.current) {
setState(s => ({ ...s, status: 'error', error }));
}
throw error;
} finally {
if (isMountedRef.current) {
setState(s => ({ ...s, isLoading: false }));
}
}
},
[callback]
);
return { execute, ...state };
};
export default useAsync;

View File

@ -1,27 +1,16 @@
import { useToast } from '@chakra-ui/react';
import { IApiRespose, IToken } from '@educt/interfaces';
import { useState } from 'react';
import { useErrorHandler } from 'react-error-boundary';
import useIsMountedRef from './useIsMountedRef';
import useAsync from './useAsync';
import { useRootStore } from './useRootStore';
type LoginStateType = {
result: IApiRespose<IToken> | null;
error: any;
loading: boolean;
fetched: boolean;
};
const useLoginQuery = () => {
const isMountedRef = useIsMountedRef();
const { authStore } = useRootStore();
const [state, setState] = useState<LoginStateType>({ result: null, error: null, loading: false, fetched: false });
const toast = useToast();
const handleError = useErrorHandler();
const login = async (login: string, password: string) => {
try {
setState(s => ({ ...s, loading: true }));
const result = await authStore.login(login, password);
toast({
title: `👋 Welcome back!`,
@ -29,14 +18,8 @@ const useLoginQuery = () => {
isClosable: true,
status: 'success',
});
if (isMountedRef.current) {
setState(s => ({ ...s, result }));
}
return result;
} catch (error: any) {
if (isMountedRef.current) {
setState(s => ({ ...s, error }));
}
if (error.response) {
/**
* Handle response error
@ -44,26 +27,23 @@ const useLoginQuery = () => {
switch (error.response.status) {
case 404:
toast({ title: 'User not found in a system.', status: 'error', duration: 2000 });
break;
throw error;
case 401:
toast({ title: 'Invalid password.', status: 'error', duration: 2000 });
break;
throw error;
default:
handleError(error);
break;
}
} else {
console.error(error);
handleError(error);
}
} finally {
if (isMountedRef.current) {
setState(s => ({ ...s, loading: false, fetched: true }));
}
}
};
return { ...state, login };
const { execute, ...state } = useAsync<IApiRespose<IToken> | undefined, Parameters<typeof login>>(login);
return { login: execute, ...state };
};
export default useLoginQuery;

View File

@ -27,6 +27,7 @@ export interface ICourse {
category: ICategory;
lessons: ILesson[];
students: Array<Pick<IUser, 'id' | 'first_name' | 'fullname' | 'last_name' | 'email'>>;
color: ColorType | null;
students_count: string;
likes_count: string;
lessons_count: string;

View File

@ -44,14 +44,12 @@ const AuthForm: React.FC = () => {
} = useForm<AuthInputType>({
resolver: yupResolver(LoginSchema),
});
const { login, loading } = useLoginQuery();
const { login, isLoading } = useLoginQuery();
/**
* Login handler
*/
const onSubmit: SubmitHandler<AuthInputType> = data => {
login(data.login, data.password).finally(() => reset());
};
const onSubmit: SubmitHandler<AuthInputType> = data => login(data.login, data.password).finally(() => reset());
return (
<Box flexBasis='500px' borderWidth='1px' borderRadius='lg' p='8'>
@ -87,7 +85,7 @@ const AuthForm: React.FC = () => {
<Button
loadingText='Logging in...'
isLoading={loading}
isLoading={isLoading}
type='submit'
colorScheme='blue'
variant='outline'

View File

@ -23,7 +23,7 @@ import CourseStatusBadge from '@educt/components/CourseStatusBadge';
* Hooks
*/
import { useHistory } from 'react-router';
import { useColorMode } from '@chakra-ui/color-mode';
import { useColorMode, useColorModeValue } from '@chakra-ui/color-mode';
import { useRootStore } from '@educt/hooks/useRootStore';
export type CourseItemPropsType = {
@ -108,7 +108,7 @@ const CourseItem: React.FC<CourseItemPropsType> = ({ course, onDelete, onSetStat
<Box
position='relative'
minHeight='200px'
backgroundColor={colorMode === 'dark' ? 'gray.700' : 'gray.200'}
backgroundColor={course.color?.hex || useColorModeValue('gray.200', 'gray.700')}
borderTopLeftRadius='lg'
borderTopRightRadius='lg'
p='10px'

View File

@ -69,7 +69,7 @@ const LessonItem: React.FC<LessonItemPropsType> = ({ lesson }) => {
<Icon as={MdTimer} />
</Text>
<Text as='small' verticalAlign='middle'>
~{moment(lesson.duration, 'HH:mm:ss').minutes()}min
~{moment.duration(lesson.duration, 'minutes').humanize()}
</Text>
</Text>
<Text as='span' color='gray.500'>