From 62b4b53fb4085e9a21167532958e950331dbfb02 Mon Sep 17 00:00:00 2001 From: Kacper Donat Date: Tue, 8 Sep 2020 20:54:05 +0200 Subject: [PATCH] Add async state management --- src/api/edition.ts | 3 ++ src/helpers.ts | 4 +++ src/hooks/index.ts | 1 + src/hooks/useAsync.ts | 50 ++++++++++++++++++++++++++++++++++ src/pages/edition/register.tsx | 32 +++++++++++++--------- 5 files changed, 77 insertions(+), 13 deletions(-) create mode 100644 src/hooks/useAsync.ts diff --git a/src/api/edition.ts b/src/api/edition.ts index 966a036..bce8982 100644 --- a/src/api/edition.ts +++ b/src/api/edition.ts @@ -1,6 +1,7 @@ import { axios } from "@/api/index"; import { Edition } from "@/data/edition"; import { sampleEdition } from "@/provider/dummy"; +import { delay } from "@/helpers"; const EDITIONS_ENDPOINT = "/editions"; const EDITION_INFO_ENDPOINT = "/editions/:key"; @@ -24,6 +25,8 @@ export async function join(key: string): Promise { // MOCK export async function get(key: string): Promise { + await delay(Math.random() * 200 + 100); + if (key == "inf2020") { return sampleEdition; } diff --git a/src/helpers.ts b/src/helpers.ts index b24217f..ae9da92 100644 --- a/src/helpers.ts +++ b/src/helpers.ts @@ -8,3 +8,7 @@ export type Index = string | symbol | number; export interface DOMEvent extends Event { target: TTarget; } + +export function delay(time: number) { + return new Promise(resolve => setTimeout(resolve, time)); +} diff --git a/src/hooks/index.ts b/src/hooks/index.ts index 9e7f847..848cfd7 100644 --- a/src/hooks/index.ts +++ b/src/hooks/index.ts @@ -1,2 +1,3 @@ export * from "./useProxyState" export * from "./useUpdateEffect" +export * from "./useAsync" diff --git a/src/hooks/useAsync.ts b/src/hooks/useAsync.ts new file mode 100644 index 0000000..91e1de2 --- /dev/null +++ b/src/hooks/useAsync.ts @@ -0,0 +1,50 @@ +import { useEffect, useState } from "react"; + +export type AsyncResult = { + isLoading: boolean, + value: T | undefined, + error: TError | undefined +}; + +export type AsyncState = [AsyncResult, (promise: Promise | undefined) => void] + +export function useAsync(promise: Promise | undefined): AsyncResult { + const [isLoading, setLoading] = useState(true); + const [error, setError] = useState(undefined); + const [value, setValue] = useState(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(initial: Promise | undefined): AsyncState { + const [promise, setPromise] = useState | undefined>(initial); + const asyncState = useAsync(promise); + + return [ asyncState, setPromise ]; +} diff --git a/src/pages/edition/register.tsx b/src/pages/edition/register.tsx index a10a105..7c69834 100644 --- a/src/pages/edition/register.tsx +++ b/src/pages/edition/register.tsx @@ -1,11 +1,12 @@ import React, { useEffect, useState } from "react"; import { Page } from "@/pages/base"; 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 { useVerticalSpacing } from "@/styles"; import { Edition } from "@/data/edition"; +import { useAsyncState } from "@/hooks"; import { Label, Section } from "@/components/section"; import { Alert } from "@material-ui/lab"; @@ -13,12 +14,12 @@ export const RegisterEditionPage = () => { const { t } = useTranslation(); const [key, setKey] = useState(""); - const [edition, setEdition] = useState(null); + const [{ value: edition, isLoading }, setEdition] = useAsyncState(undefined); const classes = useVerticalSpacing(3); useEffect(() => { - (async () => setEdition(await api.edition.get(key)))(); + setEdition(api.edition.get(key)); }, [ key ]) const handleRegister = () => { @@ -33,6 +34,17 @@ export const RegisterEditionPage = () => { setKey(ev.currentTarget.value); } + const Edition = () => edition + ?
+ + { edition.course.name } + + { t('internship.date-range', { start: edition.startDate, end: edition.endDate }) } + +
+ : { t("forms.edition-register.edition-not-found") } + + return { t("pages.edition.header") } @@ -42,16 +54,10 @@ export const RegisterEditionPage = () => { - { edition - ?
- - { edition.course.name } - - { t('internship.date-range', { start: edition.startDate, end: edition.endDate }) } - -
- : { t("forms.edition-register.edition-not-found") } - } + + + { isLoading ? : } +