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

@@ -4,8 +4,8 @@ import { useForm } from "@mantine/form"
import { client, errorToStrings } from "app/client"
import { UserModel } from "app/types"
import { Alert } from "components/Alert"
import { useAsyncCallback } from "react-async-hook"
import { TbDeviceFloppy } from "react-icons/tb"
import useMutation from "use-mutation"
interface UserEditProps {
user?: UserModel
@@ -17,18 +17,17 @@ export function UserEdit(props: UserEditProps) {
const form = useForm<UserModel>({
initialValues: props.user ?? ({ enabled: true } as UserModel),
})
const [saveUser, saveUserResult] = useMutation(client.admin.saveUser, { onSuccess: props.onSave })
const errors = errorToStrings(saveUserResult.error)
const saveUser = useAsyncCallback(client.admin.saveUser, { onSuccess: props.onSave })
return (
<>
{errors.length > 0 && (
{saveUser.error && (
<Box mb="md">
<Alert messages={errors} />
<Alert messages={errorToStrings(saveUser.error)} />
</Box>
)}
<form onSubmit={form.onSubmit(saveUser)}>
<form onSubmit={form.onSubmit(saveUser.execute)}>
<Stack>
<TextInput label={t`Name`} {...form.getInputProps("name")} required />
<PasswordInput label={t`Password`} {...form.getInputProps("password")} required={!props.user} />
@@ -40,7 +39,7 @@ export function UserEdit(props: UserEditProps) {
<Button variant="default" onClick={props.onCancel}>
<Trans>Cancel</Trans>
</Button>
<Button type="submit" leftIcon={<TbDeviceFloppy size={16} />} loading={saveUserResult.status === "running"}>
<Button type="submit" leftIcon={<TbDeviceFloppy size={16} />} loading={saveUser.loading}>
<Trans>Save</Trans>
</Button>
</Group>

View File

@@ -7,8 +7,8 @@ import { reloadTree } from "app/slices/tree"
import { useAppDispatch } from "app/store"
import { AddCategoryRequest } from "app/types"
import { Alert } from "components/Alert"
import { useAsyncCallback } from "react-async-hook"
import { TbFolderPlus } from "react-icons/tb"
import useMutation from "use-mutation"
import { CategorySelect } from "./CategorySelect"
export function AddCategory() {
@@ -16,23 +16,22 @@ export function AddCategory() {
const form = useForm<AddCategoryRequest>()
const [addCategory, addCategoryResult] = useMutation(client.category.add, {
const addCategory = useAsyncCallback(client.category.add, {
onSuccess: () => {
dispatch(reloadTree())
dispatch(redirectToSelectedSource())
},
})
const errors = errorToStrings(addCategoryResult.error)
return (
<>
{errors.length > 0 && (
{addCategory.error && (
<Box mb="md">
<Alert messages={errors} />
<Alert messages={errorToStrings(addCategory.error)} />
</Box>
)}
<form onSubmit={form.onSubmit(addCategory)}>
<form onSubmit={form.onSubmit(addCategory.execute)}>
<Stack>
<TextInput label={t`Category`} placeholder={t`Category`} {...form.getInputProps("name")} required />
<CategorySelect label={t`Parent`} {...form.getInputProps("parentId")} clearable />
@@ -40,7 +39,7 @@ export function AddCategory() {
<Button variant="default" onClick={() => dispatch(redirectToSelectedSource())}>
<Trans>Cancel</Trans>
</Button>
<Button type="submit" leftIcon={<TbFolderPlus size={16} />} loading={addCategoryResult.status === "running"}>
<Button type="submit" leftIcon={<TbFolderPlus size={16} />} loading={addCategory.loading}>
<Trans>Add</Trans>
</Button>
</Group>

View File

@@ -6,8 +6,8 @@ import { redirectToSelectedSource } from "app/slices/redirect"
import { reloadTree } from "app/slices/tree"
import { useAppDispatch } from "app/store"
import { Alert } from "components/Alert"
import { useAsyncCallback } from "react-async-hook"
import { TbFileImport } from "react-icons/tb"
import useMutation from "use-mutation"
export function ImportOpml() {
const dispatch = useAppDispatch()
@@ -18,23 +18,22 @@ export function ImportOpml() {
},
})
const [importOpml, importOpmlResult] = useMutation(client.feed.importOpml, {
const importOpml = useAsyncCallback(client.feed.importOpml, {
onSuccess: () => {
dispatch(reloadTree())
dispatch(redirectToSelectedSource())
},
})
const errors = errorToStrings(importOpmlResult.error)
return (
<>
{errors.length > 0 && (
{importOpml.error && (
<Box mb="md">
<Alert messages={errors} />
<Alert messages={errorToStrings(importOpml.error)} />
</Box>
)}
<form onSubmit={form.onSubmit(v => importOpml(v.file))}>
<form onSubmit={form.onSubmit(v => importOpml.execute(v.file))}>
<Stack>
<FileInput
label={t`OPML file`}
@@ -48,7 +47,7 @@ export function ImportOpml() {
<Button variant="default" onClick={() => dispatch(redirectToSelectedSource())}>
<Trans>Cancel</Trans>
</Button>
<Button type="submit" leftIcon={<TbFileImport size={16} />} loading={importOpmlResult.status === "running"}>
<Button type="submit" leftIcon={<TbFileImport size={16} />} loading={importOpml.loading}>
<Trans>Import</Trans>
</Button>
</Group>

View File

@@ -1,7 +1,7 @@
import { t, Trans } from "@lingui/macro"
import { Box, Button, Group, Stack, Stepper, TextInput } from "@mantine/core"
import { useForm } from "@mantine/form"
import { client, errorsToStrings, errorToStrings } from "app/client"
import { client, errorToStrings } from "app/client"
import { Constants } from "app/constants"
import { redirectToFeed, redirectToSelectedSource } from "app/slices/redirect"
import { reloadTree } from "app/slices/tree"
@@ -9,8 +9,8 @@ import { useAppDispatch } from "app/store"
import { FeedInfoRequest, SubscribeRequest } from "app/types"
import { Alert } from "components/Alert"
import { useState } from "react"
import { useAsyncCallback } from "react-async-hook"
import { TbRss } from "react-icons/tb"
import useMutation from "use-mutation"
import { CategorySelect } from "./CategorySelect"
export function Subscribe() {
@@ -31,20 +31,19 @@ export function Subscribe() {
},
})
const [fetchFeed, fetchFeedResult] = useMutation(client.feed.fetchFeed, {
const fetchFeed = useAsyncCallback(client.feed.fetchFeed, {
onSuccess: ({ data }) => {
step1Form.setFieldValue("url", data.data.url)
step1Form.setFieldValue("title", data.data.title)
step1Form.setFieldValue("url", data.url)
step1Form.setFieldValue("title", data.title)
setActiveStep(step => step + 1)
},
})
const [subscribe, subscribeResult] = useMutation(client.feed.subscribe, {
const subscribe = useAsyncCallback(client.feed.subscribe, {
onSuccess: sub => {
dispatch(reloadTree())
dispatch(redirectToFeed(sub.data.data.id))
dispatch(redirectToFeed(sub.data.id))
},
})
const errors = errorsToStrings([fetchFeedResult.error, errorToStrings(subscribeResult.error)])
const previousStep = () => {
if (activeStep === 0) dispatch(redirectToSelectedSource())
@@ -52,17 +51,23 @@ export function Subscribe() {
}
const nextStep = (e: React.FormEvent<HTMLFormElement>) => {
if (activeStep === 0) {
step0Form.onSubmit(fetchFeed)(e)
step0Form.onSubmit(fetchFeed.execute)(e)
} else if (activeStep === 1) {
step1Form.onSubmit(subscribe)(e)
step1Form.onSubmit(subscribe.execute)(e)
}
}
return (
<>
{errors.length > 0 && (
{fetchFeed.error && (
<Box mb="md">
<Alert messages={errors} />
<Alert messages={errorToStrings(fetchFeed.error)} />
</Box>
)}
{subscribe.error && (
<Box mb="md">
<Alert messages={errorToStrings(subscribe.error)} />
</Box>
)}
@@ -96,16 +101,12 @@ export function Subscribe() {
<Trans>Back</Trans>
</Button>
{activeStep === 0 && (
<Button type="submit" loading={fetchFeedResult.status === "running"}>
<Button type="submit" loading={fetchFeed.loading}>
<Trans>Next</Trans>
</Button>
)}
{activeStep === 1 && (
<Button
type="submit"
leftIcon={<TbRss size={16} />}
loading={fetchFeedResult.status === "running" || subscribeResult.status === "running"}
>
<Button type="submit" leftIcon={<TbRss size={16} />} loading={fetchFeed.loading || subscribe.loading}>
<Trans>Subscribe</Trans>
</Button>
)}

View File

@@ -2,15 +2,15 @@ import { t, Trans } from "@lingui/macro"
import { Anchor, Box, Button, Checkbox, Divider, Group, Input, PasswordInput, Stack, Text, TextInput } 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 { redirectToLogin, redirectToSelectedSource } from "app/slices/redirect"
import { reloadProfile } from "app/slices/user"
import { useAppDispatch, useAppSelector } from "app/store"
import { ProfileModificationRequest } from "app/types"
import { Alert } from "components/Alert"
import { useEffect } from "react"
import { useAsyncCallback } from "react-async-hook"
import { TbDeviceFloppy, TbTrash } from "react-icons/tb"
import useMutation from "use-mutation"
interface FormData extends ProfileModificationRequest {
newPasswordConfirmation?: string
@@ -27,18 +27,17 @@ export function ProfileSettings() {
})
const { setValues } = form
const [saveProfile, saveProfileResult] = useMutation(client.user.saveProfile, {
const saveProfile = useAsyncCallback(client.user.saveProfile, {
onSuccess: () => {
dispatch(reloadProfile())
dispatch(redirectToSelectedSource())
},
})
const [deleteProfile, deleteProfileResult] = useMutation(client.user.deleteProfile, {
const deleteProfile = useAsyncCallback(client.user.deleteProfile, {
onSuccess: () => {
dispatch(redirectToLogin())
},
})
const errors = errorsToStrings([saveProfileResult.error, deleteProfileResult.error])
const openDeleteProfileModal = () =>
openConfirmModal({
@@ -50,7 +49,7 @@ export function ProfileSettings() {
),
labels: { confirm: t`Confirm`, cancel: t`Cancel` },
confirmProps: { color: "red" },
onConfirm: () => deleteProfile({}),
onConfirm: () => deleteProfile.execute(),
})
useEffect(() => {
@@ -64,13 +63,19 @@ export function ProfileSettings() {
return (
<>
{errors.length > 0 && (
{saveProfile.error && (
<Box mb="md">
<Alert messages={errors} />
<Alert messages={errorToStrings(saveProfile.error)} />
</Box>
)}
<form onSubmit={form.onSubmit(saveProfile)}>
{deleteProfile.error && (
<Box mb="md">
<Alert messages={errorToStrings(deleteProfile.error)} />
</Box>
)}
<form onSubmit={form.onSubmit(saveProfile.execute)}>
<Stack>
<Input.Wrapper label={t`User name`}>
<Box>{profile?.name}</Box>
@@ -105,7 +110,7 @@ export function ProfileSettings() {
<Button variant="default" onClick={() => dispatch(redirectToSelectedSource())}>
<Trans>Cancel</Trans>
</Button>
<Button type="submit" leftIcon={<TbDeviceFloppy size={16} />} loading={saveProfileResult.status === "running"}>
<Button type="submit" leftIcon={<TbDeviceFloppy size={16} />} loading={saveProfile.loading}>
<Trans>Save</Trans>
</Button>
<Divider orientation="vertical" />
@@ -113,7 +118,7 @@ export function ProfileSettings() {
color="red"
leftIcon={<TbTrash size={16} />}
onClick={() => openDeleteProfileModal()}
loading={deleteProfileResult.status === "running"}
loading={deleteProfile.loading}
>
<Trans>Delete account</Trans>
</Button>