mirror of
https://github.com/sergeyyarkov/educt-client.git
synced 2026-10-11 18:29:15 +03:00
feat: added useAsync hook
This commit is contained in:
parent
80ddcead4f
commit
bf55565eed
41
src/hooks/useAsync.ts
Normal file
41
src/hooks/useAsync.ts
Normal 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;
|
||||||
@ -1,27 +1,16 @@
|
|||||||
import { useToast } from '@chakra-ui/react';
|
import { useToast } from '@chakra-ui/react';
|
||||||
import { IApiRespose, IToken } from '@educt/interfaces';
|
import { IApiRespose, IToken } from '@educt/interfaces';
|
||||||
import { useState } from 'react';
|
|
||||||
import { useErrorHandler } from 'react-error-boundary';
|
import { useErrorHandler } from 'react-error-boundary';
|
||||||
import useIsMountedRef from './useIsMountedRef';
|
import useAsync from './useAsync';
|
||||||
import { useRootStore } from './useRootStore';
|
import { useRootStore } from './useRootStore';
|
||||||
|
|
||||||
type LoginStateType = {
|
|
||||||
result: IApiRespose<IToken> | null;
|
|
||||||
error: any;
|
|
||||||
loading: boolean;
|
|
||||||
fetched: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
const useLoginQuery = () => {
|
const useLoginQuery = () => {
|
||||||
const isMountedRef = useIsMountedRef();
|
|
||||||
const { authStore } = useRootStore();
|
const { authStore } = useRootStore();
|
||||||
const [state, setState] = useState<LoginStateType>({ result: null, error: null, loading: false, fetched: false });
|
|
||||||
const toast = useToast();
|
const toast = useToast();
|
||||||
const handleError = useErrorHandler();
|
const handleError = useErrorHandler();
|
||||||
|
|
||||||
const login = async (login: string, password: string) => {
|
const login = async (login: string, password: string) => {
|
||||||
try {
|
try {
|
||||||
setState(s => ({ ...s, loading: true }));
|
|
||||||
const result = await authStore.login(login, password);
|
const result = await authStore.login(login, password);
|
||||||
toast({
|
toast({
|
||||||
title: `👋 Welcome back!`,
|
title: `👋 Welcome back!`,
|
||||||
@ -29,14 +18,8 @@ const useLoginQuery = () => {
|
|||||||
isClosable: true,
|
isClosable: true,
|
||||||
status: 'success',
|
status: 'success',
|
||||||
});
|
});
|
||||||
|
return result;
|
||||||
if (isMountedRef.current) {
|
|
||||||
setState(s => ({ ...s, result }));
|
|
||||||
}
|
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
if (isMountedRef.current) {
|
|
||||||
setState(s => ({ ...s, error }));
|
|
||||||
}
|
|
||||||
if (error.response) {
|
if (error.response) {
|
||||||
/**
|
/**
|
||||||
* Handle response error
|
* Handle response error
|
||||||
@ -44,26 +27,23 @@ const useLoginQuery = () => {
|
|||||||
switch (error.response.status) {
|
switch (error.response.status) {
|
||||||
case 404:
|
case 404:
|
||||||
toast({ title: 'User not found in a system.', status: 'error', duration: 2000 });
|
toast({ title: 'User not found in a system.', status: 'error', duration: 2000 });
|
||||||
break;
|
throw error;
|
||||||
case 401:
|
case 401:
|
||||||
toast({ title: 'Invalid password.', status: 'error', duration: 2000 });
|
toast({ title: 'Invalid password.', status: 'error', duration: 2000 });
|
||||||
break;
|
throw error;
|
||||||
default:
|
default:
|
||||||
handleError(error);
|
handleError(error);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
console.error(error);
|
|
||||||
handleError(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;
|
export default useLoginQuery;
|
||||||
|
|||||||
@ -27,6 +27,7 @@ export interface ICourse {
|
|||||||
category: ICategory;
|
category: ICategory;
|
||||||
lessons: ILesson[];
|
lessons: ILesson[];
|
||||||
students: Array<Pick<IUser, 'id' | 'first_name' | 'fullname' | 'last_name' | 'email'>>;
|
students: Array<Pick<IUser, 'id' | 'first_name' | 'fullname' | 'last_name' | 'email'>>;
|
||||||
|
color: ColorType | null;
|
||||||
students_count: string;
|
students_count: string;
|
||||||
likes_count: string;
|
likes_count: string;
|
||||||
lessons_count: string;
|
lessons_count: string;
|
||||||
|
|||||||
@ -44,14 +44,12 @@ const AuthForm: React.FC = () => {
|
|||||||
} = useForm<AuthInputType>({
|
} = useForm<AuthInputType>({
|
||||||
resolver: yupResolver(LoginSchema),
|
resolver: yupResolver(LoginSchema),
|
||||||
});
|
});
|
||||||
const { login, loading } = useLoginQuery();
|
const { login, isLoading } = useLoginQuery();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Login handler
|
* Login handler
|
||||||
*/
|
*/
|
||||||
const onSubmit: SubmitHandler<AuthInputType> = data => {
|
const onSubmit: SubmitHandler<AuthInputType> = data => login(data.login, data.password).finally(() => reset());
|
||||||
login(data.login, data.password).finally(() => reset());
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box flexBasis='500px' borderWidth='1px' borderRadius='lg' p='8'>
|
<Box flexBasis='500px' borderWidth='1px' borderRadius='lg' p='8'>
|
||||||
@ -87,7 +85,7 @@ const AuthForm: React.FC = () => {
|
|||||||
|
|
||||||
<Button
|
<Button
|
||||||
loadingText='Logging in...'
|
loadingText='Logging in...'
|
||||||
isLoading={loading}
|
isLoading={isLoading}
|
||||||
type='submit'
|
type='submit'
|
||||||
colorScheme='blue'
|
colorScheme='blue'
|
||||||
variant='outline'
|
variant='outline'
|
||||||
|
|||||||
@ -23,7 +23,7 @@ import CourseStatusBadge from '@educt/components/CourseStatusBadge';
|
|||||||
* Hooks
|
* Hooks
|
||||||
*/
|
*/
|
||||||
import { useHistory } from 'react-router';
|
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';
|
import { useRootStore } from '@educt/hooks/useRootStore';
|
||||||
|
|
||||||
export type CourseItemPropsType = {
|
export type CourseItemPropsType = {
|
||||||
@ -108,7 +108,7 @@ const CourseItem: React.FC<CourseItemPropsType> = ({ course, onDelete, onSetStat
|
|||||||
<Box
|
<Box
|
||||||
position='relative'
|
position='relative'
|
||||||
minHeight='200px'
|
minHeight='200px'
|
||||||
backgroundColor={colorMode === 'dark' ? 'gray.700' : 'gray.200'}
|
backgroundColor={course.color?.hex || useColorModeValue('gray.200', 'gray.700')}
|
||||||
borderTopLeftRadius='lg'
|
borderTopLeftRadius='lg'
|
||||||
borderTopRightRadius='lg'
|
borderTopRightRadius='lg'
|
||||||
p='10px'
|
p='10px'
|
||||||
|
|||||||
@ -69,7 +69,7 @@ const LessonItem: React.FC<LessonItemPropsType> = ({ lesson }) => {
|
|||||||
<Icon as={MdTimer} />
|
<Icon as={MdTimer} />
|
||||||
</Text>
|
</Text>
|
||||||
<Text as='small' verticalAlign='middle'>
|
<Text as='small' verticalAlign='middle'>
|
||||||
~{moment(lesson.duration, 'HH:mm:ss').minutes()}min
|
~{moment.duration(lesson.duration, 'minutes').humanize()}
|
||||||
</Text>
|
</Text>
|
||||||
</Text>
|
</Text>
|
||||||
<Text as='span' color='gray.500'>
|
<Text as='span' color='gray.500'>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user