react-async-hook library provides useAsyncCallback that does the same thing as useMutation

This commit is contained in:
Athou
2022-08-13 18:38:11 +02:00
parent 6c6cc8d85b
commit 906acb217a
15 changed files with 122 additions and 152 deletions

View File

@@ -7,9 +7,8 @@ import { UserEdit } from "components/admin/UserEdit"
import { Alert } from "components/Alert"
import { Loader } from "components/Loader"
import { RelativeDate } from "components/RelativeDate"
import { useAsync } from "react-async-hook"
import { useAsync, useAsyncCallback } from "react-async-hook"
import { TbCheck, TbPencil, TbPlus, TbTrash, TbX } from "react-icons/tb"
import useMutation from "use-mutation"
function BooleanIcon({ value }: { value: boolean }) {
return value ? <TbCheck size={18} /> : <TbX size={18} />
@@ -20,13 +19,12 @@ export function AdminUsersPage() {
const query = useAsync(() => client.admin.getAllUsers(), [])
const users = query.result?.data.sort((a, b) => a.id - b.id)
const [deleteUser, deleteUserResult] = useMutation(client.admin.deleteUser, {
const deleteUser = useAsyncCallback(client.admin.deleteUser, {
onSuccess: () => {
query.execute()
closeAllModals()
},
})
const errors = errorToStrings(deleteUserResult.error)
const openUserEditModal = (title: string, user?: UserModel) => {
openModal({
@@ -57,7 +55,7 @@ export function AdminUsersPage() {
),
labels: { confirm: t`Confirm`, cancel: t`Cancel` },
confirmProps: { color: "red" },
onConfirm: () => deleteUser({ id: user.id }),
onConfirm: () => deleteUser.execute({ id: user.id }),
})
}
@@ -73,9 +71,9 @@ export function AdminUsersPage() {
</Group>
</Title>
{errors.length > 0 && (
{deleteUser.error && (
<Box mb="md">
<Alert messages={errors} />
<Alert messages={errorToStrings(deleteUser.error)} />
</Box>
)}
@@ -134,7 +132,7 @@ export function AdminUsersPage() {
<ActionIcon
color={theme.primaryColor}
onClick={() => openUserDeleteModal(u)}
loading={deleteUserResult.status === "running"}
loading={deleteUser.loading}
>
<TbTrash size={18} />
</ActionIcon>

View File

@@ -13,35 +13,34 @@ import { Alert } from "components/Alert"
import { CategorySelect } from "components/content/add/CategorySelect"
import { Loader } from "components/Loader"
import { useEffect } from "react"
import { useAsync } from "react-async-hook"
import { useAsync, useAsyncCallback } from "react-async-hook"
import { TbDeviceFloppy, TbTrash } from "react-icons/tb"
import { useParams } from "react-router-dom"
import useMutation from "use-mutation"
export function CategoryDetailsPage() {
const { id = Constants.categoryIds.all } = useParams()
const apiKey = useAppSelector(state => state.user.profile?.apiKey)
const dispatch = useAppDispatch()
const query = useAsync(() => client.category.getRoot(), [])
const category = query.result && flattenCategoryTree(query.result.data).find(c => c.id === id)
const form = useForm<CategoryModificationRequest>()
const { setValues } = form
const [modify, modifyResult] = useMutation(client.category.modify, {
const modifyCategory = useAsyncCallback(client.category.modify, {
onSuccess: () => {
dispatch(reloadTree())
dispatch(redirectToSelectedSource())
},
})
const [deleteCategory, deleteCategoryResult] = useMutation(client.category.delete, {
const deleteCategory = useAsyncCallback(client.category.delete, {
onSuccess: () => {
dispatch(reloadTree())
dispatch(redirectToRootCategory())
},
})
const errors = [...errorToStrings(modifyResult.error), ...errorToStrings(deleteCategoryResult.error)]
const openDeleteCategoryModal = () => {
const categoryName = category?.name
@@ -56,7 +55,7 @@ export function CategoryDetailsPage() {
),
labels: { confirm: t`Confirm`, cancel: t`Cancel` },
confirmProps: { color: "red" },
onConfirm: () => deleteCategory({ id: +id }),
onConfirm: () => deleteCategory.execute({ id: +id }),
})
}
@@ -73,13 +72,19 @@ export function CategoryDetailsPage() {
if (!category) return <Loader />
return (
<Container>
{errors.length > 0 && (
{modifyCategory.error && (
<Box mb="md">
<Alert messages={errors} />
<Alert messages={errorToStrings(modifyCategory.error)} />
</Box>
)}
<form onSubmit={form.onSubmit(modify)}>
{deleteCategory.error && (
<Box mb="md">
<Alert messages={errorToStrings(deleteCategory.error)} />
</Box>
)}
<form onSubmit={form.onSubmit(modifyCategory.execute)}>
<Stack>
<Title order={3}>{category.name}</Title>
<Input.Wrapper label={t`Generated feed url`}>
@@ -105,7 +110,7 @@ export function CategoryDetailsPage() {
<Button variant="default" onClick={() => dispatch(redirectToSelectedSource())}>
<Trans>Cancel</Trans>
</Button>
<Button type="submit" leftIcon={<TbDeviceFloppy size={16} />} loading={modifyResult.status === "running"}>
<Button type="submit" leftIcon={<TbDeviceFloppy size={16} />} loading={modifyCategory.loading}>
<Trans>Save</Trans>
</Button>
<Divider orientation="vertical" />
@@ -113,7 +118,7 @@ export function CategoryDetailsPage() {
color="red"
leftIcon={<TbTrash size={16} />}
onClick={() => openDeleteCategoryModal()}
loading={deleteCategoryResult.status === "running"}
loading={deleteCategory.loading}
>
<Trans>Delete</Trans>
</Button>

View File

@@ -2,7 +2,7 @@ import { t, Trans } from "@lingui/macro"
import { Anchor, Box, Button, Code, Container, Divider, Group, Input, NumberInput, Stack, Text, TextInput, Title } from "@mantine/core"
import { useForm } from "@mantine/form"
import { openConfirmModal } from "@mantine/modals"
import { client, errorsToStrings } from "app/client"
import { client, errorToStrings } from "app/client"
import { redirectToRootCategory, redirectToSelectedSource } from "app/slices/redirect"
import { reloadTree } from "app/slices/tree"
import { useAppDispatch, useAppSelector } from "app/store"
@@ -12,10 +12,9 @@ import { CategorySelect } from "components/content/add/CategorySelect"
import { Loader } from "components/Loader"
import { RelativeDate } from "components/RelativeDate"
import { useEffect } from "react"
import { useAsync } from "react-async-hook"
import { useAsync, useAsyncCallback } from "react-async-hook"
import { TbDeviceFloppy, TbTrash } from "react-icons/tb"
import { useParams } from "react-router-dom"
import useMutation from "use-mutation"
function FilteringExpressionDescription() {
const example = <Code>url.contains('youtube') or (author eq 'athou' and title.contains('github')</Code>
@@ -60,19 +59,18 @@ export function FeedDetailsPage() {
const form = useForm<FeedModificationRequest>()
const { setValues } = form
const [modify, modifyResult] = useMutation(client.feed.modify, {
const modifyFeed = useAsyncCallback(client.feed.modify, {
onSuccess: () => {
dispatch(reloadTree())
dispatch(redirectToSelectedSource())
},
})
const [unsubscribe, unsubscribeResult] = useMutation(client.feed.unsubscribe, {
const unsubscribe = useAsyncCallback(client.feed.unsubscribe, {
onSuccess: () => {
dispatch(reloadTree())
dispatch(redirectToRootCategory())
},
})
const errors = errorsToStrings([modifyResult.error, unsubscribeResult.error])
const openUnsubscribeModal = () => {
const feedName = feed?.name
@@ -87,7 +85,7 @@ export function FeedDetailsPage() {
),
labels: { confirm: t`Confirm`, cancel: t`Cancel` },
confirmProps: { color: "red" },
onConfirm: () => unsubscribe({ id: +id }),
onConfirm: () => unsubscribe.execute({ id: +id }),
})
}
@@ -99,13 +97,19 @@ export function FeedDetailsPage() {
if (!feed) return <Loader />
return (
<Container>
{errors.length > 0 && (
{modifyFeed.error && (
<Box mb="md">
<Alert messages={errors} />
<Alert messages={errorToStrings(modifyFeed.error)} />
</Box>
)}
<form onSubmit={form.onSubmit(modify)}>
{unsubscribe.error && (
<Box mb="md">
<Alert messages={errorToStrings(unsubscribe.error)} />
</Box>
)}
<form onSubmit={form.onSubmit(modifyFeed.execute)}>
<Stack>
<Title order={3}>{feed.name}</Title>
<Input.Wrapper label={t`Feed URL`}>
@@ -159,7 +163,7 @@ export function FeedDetailsPage() {
<Button variant="default" onClick={() => dispatch(redirectToSelectedSource())}>
<Trans>Cancel</Trans>
</Button>
<Button type="submit" leftIcon={<TbDeviceFloppy size={16} />} loading={modifyResult.status === "running"}>
<Button type="submit" leftIcon={<TbDeviceFloppy size={16} />} loading={modifyFeed.loading}>
<Trans>Save</Trans>
</Button>
<Divider orientation="vertical" />
@@ -167,7 +171,7 @@ export function FeedDetailsPage() {
color="red"
leftIcon={<TbTrash size={16} />}
onClick={() => openUnsubscribeModal()}
loading={unsubscribeResult.status === "running"}
loading={unsubscribe.loading}
>
<Trans>Unsubscribe</Trans>
</Button>

View File

@@ -7,8 +7,8 @@ import { useAppDispatch, useAppSelector } from "app/store"
import { LoginRequest } from "app/types"
import { Alert } from "components/Alert"
import { Logo } from "components/Logo"
import { useAsyncCallback } from "react-async-hook"
import { Link } from "react-router-dom"
import useMutation from "use-mutation"
export function LoginPage() {
const serverInfos = useAppSelector(state => state.server.serverInfos)
@@ -21,12 +21,11 @@ export function LoginPage() {
},
})
const [login, loginResult] = useMutation(client.user.login, {
const login = useAsyncCallback(client.user.login, {
onSuccess: () => {
dispatch(redirectToRootCategory())
},
})
const errors = errorToStrings(loginResult.error)
return (
<Container size="xs">
@@ -40,12 +39,12 @@ export function LoginPage() {
<Title order={2} mb="md">
<Trans>Log in</Trans>
</Title>
{errors.length > 0 && (
{login.error && (
<Box mb="md">
<Alert messages={errors} />
<Alert messages={errorToStrings(login.error)} />
</Box>
)}
<form onSubmit={form.onSubmit(login)}>
<form onSubmit={form.onSubmit(login.execute)}>
<Stack>
<TextInput
label={t`User Name or E-mail`}
@@ -69,7 +68,7 @@ export function LoginPage() {
</Anchor>
)}
<Button type="submit" loading={loginResult.status === "running"}>
<Button type="submit" loading={login.loading}>
<Trans>Log in</Trans>
</Button>
{serverInfos?.allowRegistrations && (

View File

@@ -6,8 +6,8 @@ import { PasswordResetRequest } from "app/types"
import { Alert } from "components/Alert"
import { Logo } from "components/Logo"
import { useState } from "react"
import { useAsyncCallback } from "react-async-hook"
import { Link } from "react-router-dom"
import useMutation from "use-mutation"
export function PasswordRecoveryPage() {
const [message, setMessage] = useState("")
@@ -18,15 +18,11 @@ export function PasswordRecoveryPage() {
},
})
const [recoverPassword, recoverPasswordResult] = useMutation(client.user.passwordReset, {
onMutate: () => {
setMessage("")
},
const recoverPassword = useAsyncCallback(client.user.passwordReset, {
onSuccess: () => {
setMessage(t`An email has been sent if this address was registered. Check your inbox.`)
},
})
const errors = errorToStrings(recoverPasswordResult.error)
return (
<Container size="xs">
@@ -40,17 +36,25 @@ export function PasswordRecoveryPage() {
<Title order={2} mb="md">
<Trans>Password Recovery</Trans>
</Title>
{errors.length > 0 && (
{recoverPassword.error && (
<Box mb="md">
<Alert messages={errors} />
<Alert messages={errorToStrings(recoverPassword.error)} />
</Box>
)}
{message && (
<Box mb="md">
<Alert level="success" messages={[message]} />
</Box>
)}
<form onSubmit={form.onSubmit(recoverPassword)}>
<form
onSubmit={form.onSubmit(req => {
setMessage("")
recoverPassword.execute(req)
})}
>
<Stack>
<TextInput
type="email"
@@ -61,7 +65,7 @@ export function PasswordRecoveryPage() {
required
/>
<Button type="submit" loading={recoverPasswordResult.status === "running"}>
<Button type="submit" loading={recoverPassword.loading}>
<Trans>Recover password</Trans>
</Button>

View File

@@ -7,8 +7,8 @@ import { useAppDispatch, useAppSelector } from "app/store"
import { RegistrationRequest } from "app/types"
import { Alert } from "components/Alert"
import { Logo } from "components/Logo"
import { useAsyncCallback } from "react-async-hook"
import { Link } from "react-router-dom"
import useMutation from "use-mutation"
export function RegistrationPage() {
const serverInfos = useAppSelector(state => state.server.serverInfos)
@@ -22,12 +22,11 @@ export function RegistrationPage() {
},
})
const [register, registerResult] = useMutation(client.user.register, {
const register = useAsyncCallback(client.user.register, {
onSuccess: () => {
dispatch(redirectToRootCategory())
},
})
const errors = errorToStrings(registerResult.error)
return (
<Container size="xs">
@@ -48,13 +47,13 @@ export function RegistrationPage() {
)}
{serverInfos?.allowRegistrations && (
<>
{errors.length > 0 && (
{register.error && (
<Box mb="md">
<Alert messages={errors} />
<Alert messages={errorToStrings(register.error)} />
</Box>
)}
<form onSubmit={form.onSubmit(register)}>
<form onSubmit={form.onSubmit(register.execute)}>
<Stack>
<TextInput label="User Name" placeholder="User Name" {...form.getInputProps("name")} size="md" required />
<TextInput
@@ -72,7 +71,7 @@ export function RegistrationPage() {
size="md"
required
/>
<Button type="submit" loading={registerResult.status === "running"}>
<Button type="submit" loading={register.loading}>
<Trans>Sign up</Trans>
</Button>
<Center>