Add async state management
This commit is contained in:
parent
3bc6f05a20
commit
62b4b53fb4
@ -1,6 +1,7 @@
|
|||||||
import { axios } from "@/api/index";
|
import { axios } from "@/api/index";
|
||||||
import { Edition } from "@/data/edition";
|
import { Edition } from "@/data/edition";
|
||||||
import { sampleEdition } from "@/provider/dummy";
|
import { sampleEdition } from "@/provider/dummy";
|
||||||
|
import { delay } from "@/helpers";
|
||||||
|
|
||||||
const EDITIONS_ENDPOINT = "/editions";
|
const EDITIONS_ENDPOINT = "/editions";
|
||||||
const EDITION_INFO_ENDPOINT = "/editions/:key";
|
const EDITION_INFO_ENDPOINT = "/editions/:key";
|
||||||
@ -24,6 +25,8 @@ export async function join(key: string): Promise<boolean> {
|
|||||||
|
|
||||||
// MOCK
|
// MOCK
|
||||||
export async function get(key: string): Promise<Edition | null> {
|
export async function get(key: string): Promise<Edition | null> {
|
||||||
|
await delay(Math.random() * 200 + 100);
|
||||||
|
|
||||||
if (key == "inf2020") {
|
if (key == "inf2020") {
|
||||||
return sampleEdition;
|
return sampleEdition;
|
||||||
}
|
}
|
||||||
|
@ -8,3 +8,7 @@ export type Index = string | symbol | number;
|
|||||||
export interface DOMEvent<TTarget extends EventTarget> extends Event {
|
export interface DOMEvent<TTarget extends EventTarget> extends Event {
|
||||||
target: TTarget;
|
target: TTarget;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function delay(time: number) {
|
||||||
|
return new Promise(resolve => setTimeout(resolve, time));
|
||||||
|
}
|
||||||
|
@ -1,2 +1,3 @@
|
|||||||
export * from "./useProxyState"
|
export * from "./useProxyState"
|
||||||
export * from "./useUpdateEffect"
|
export * from "./useUpdateEffect"
|
||||||
|
export * from "./useAsync"
|
||||||
|
50
src/hooks/useAsync.ts
Normal file
50
src/hooks/useAsync.ts
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
import { useEffect, useState } from "react";
|
||||||
|
|
||||||
|
export type AsyncResult<T, TError = any> = {
|
||||||
|
isLoading: boolean,
|
||||||
|
value: T | undefined,
|
||||||
|
error: TError | undefined
|
||||||
|
};
|
||||||
|
|
||||||
|
export type AsyncState<T, TError = any> = [AsyncResult<T, TError>, (promise: Promise<T> | undefined) => void]
|
||||||
|
|
||||||
|
export function useAsync<T, TError = any>(promise: Promise<T> | undefined): AsyncResult<T, TError> {
|
||||||
|
const [isLoading, setLoading] = useState<boolean>(true);
|
||||||
|
const [error, setError] = useState<TError | undefined>(undefined);
|
||||||
|
const [value, setValue] = useState<T | undefined>(undefined);
|
||||||
|
const [semaphore] = useState<{ value: number }>({ value: 0 })
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setLoading(true);
|
||||||
|
setError(undefined);
|
||||||
|
setValue(undefined);
|
||||||
|
|
||||||
|
const myMagicNumber = semaphore.value + 1;
|
||||||
|
semaphore.value = myMagicNumber;
|
||||||
|
|
||||||
|
promise && promise.then(value => {
|
||||||
|
if (semaphore.value == myMagicNumber) {
|
||||||
|
setValue(value);
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
}).catch(error => {
|
||||||
|
if (semaphore.value == myMagicNumber) {
|
||||||
|
setError(error);
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}, [ promise ])
|
||||||
|
|
||||||
|
return {
|
||||||
|
isLoading,
|
||||||
|
value,
|
||||||
|
error,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useAsyncState<T, TError = any>(initial: Promise<T> | undefined): AsyncState<T, TError> {
|
||||||
|
const [promise, setPromise] = useState<Promise<T> | undefined>(initial);
|
||||||
|
const asyncState = useAsync(promise);
|
||||||
|
|
||||||
|
return [ asyncState, setPromise ];
|
||||||
|
}
|
@ -1,11 +1,12 @@
|
|||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import { Page } from "@/pages/base";
|
import { Page } from "@/pages/base";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { Button, Container, TextField, Typography } from "@material-ui/core";
|
import { Box, Button, CircularProgress, Container, TextField, Typography } from "@material-ui/core";
|
||||||
|
|
||||||
import api from "@/api";
|
import api from "@/api";
|
||||||
import { useVerticalSpacing } from "@/styles";
|
import { useVerticalSpacing } from "@/styles";
|
||||||
import { Edition } from "@/data/edition";
|
import { Edition } from "@/data/edition";
|
||||||
|
import { useAsyncState } from "@/hooks";
|
||||||
import { Label, Section } from "@/components/section";
|
import { Label, Section } from "@/components/section";
|
||||||
import { Alert } from "@material-ui/lab";
|
import { Alert } from "@material-ui/lab";
|
||||||
|
|
||||||
@ -13,12 +14,12 @@ export const RegisterEditionPage = () => {
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const [key, setKey] = useState<string>("");
|
const [key, setKey] = useState<string>("");
|
||||||
const [edition, setEdition] = useState<Edition | null>(null);
|
const [{ value: edition, isLoading }, setEdition] = useAsyncState<Edition | null>(undefined);
|
||||||
|
|
||||||
const classes = useVerticalSpacing(3);
|
const classes = useVerticalSpacing(3);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
(async () => setEdition(await api.edition.get(key)))();
|
setEdition(api.edition.get(key));
|
||||||
}, [ key ])
|
}, [ key ])
|
||||||
|
|
||||||
const handleRegister = () => {
|
const handleRegister = () => {
|
||||||
@ -33,6 +34,17 @@ export const RegisterEditionPage = () => {
|
|||||||
setKey(ev.currentTarget.value);
|
setKey(ev.currentTarget.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const Edition = () => edition
|
||||||
|
? <Section>
|
||||||
|
<Label>{ t("forms.edition-register.edition" ) }</Label>
|
||||||
|
<Typography className="proposal__primary">{ edition.course.name }</Typography>
|
||||||
|
<Typography className="proposal__secondary">
|
||||||
|
{ t('internship.date-range', { start: edition.startDate, end: edition.endDate }) }
|
||||||
|
</Typography>
|
||||||
|
</Section>
|
||||||
|
: <Alert severity="warning">{ t("forms.edition-register.edition-not-found") }</Alert>
|
||||||
|
|
||||||
|
|
||||||
return <Page>
|
return <Page>
|
||||||
<Page.Header maxWidth="md">
|
<Page.Header maxWidth="md">
|
||||||
<Page.Title>{ t("pages.edition.header") }</Page.Title>
|
<Page.Title>{ t("pages.edition.header") }</Page.Title>
|
||||||
@ -42,16 +54,10 @@ export const RegisterEditionPage = () => {
|
|||||||
<TextField label={ t("forms.edition-register.fields.key") } fullWidth
|
<TextField label={ t("forms.edition-register.fields.key") } fullWidth
|
||||||
onChange={ handleKeyChange }
|
onChange={ handleKeyChange }
|
||||||
value={ key } />
|
value={ key } />
|
||||||
{ edition
|
|
||||||
? <Section>
|
<Box>
|
||||||
<Label>{ t("forms.edition-register.edition" ) }</Label>
|
{ isLoading ? <CircularProgress /> : <Edition /> }
|
||||||
<Typography className="proposal__primary">{ edition.course.name }</Typography>
|
</Box>
|
||||||
<Typography className="proposal__secondary">
|
|
||||||
{ t('internship.date-range', { start: edition.startDate, end: edition.endDate }) }
|
|
||||||
</Typography>
|
|
||||||
</Section>
|
|
||||||
: <Alert severity="warning">{ t("forms.edition-register.edition-not-found") }</Alert>
|
|
||||||
}
|
|
||||||
|
|
||||||
<Button onClick={ handleRegister } variant="contained" color="primary" disabled={ !edition }>{ t("forms.edition-register.register") }</Button>
|
<Button onClick={ handleRegister } variant="contained" color="primary" disabled={ !edition }>{ t("forms.edition-register.register") }</Button>
|
||||||
</Container>
|
</Container>
|
||||||
|
Loading…
Reference in New Issue
Block a user