Add logic for loading handling with server action
This commit is contained in:
parent
021511e60e
commit
324e7314e3
@ -1,6 +1,7 @@
|
||||
export default interface ButtonVm {
|
||||
props: {
|
||||
title: string
|
||||
title: string;
|
||||
isDisable: boolean;
|
||||
}
|
||||
onClick(): void
|
||||
}
|
@ -11,7 +11,7 @@ export default class Button extends BaseView<ButtonVm> {
|
||||
protected Build(props: BuildProps<ButtonVm>): ReactNode {
|
||||
const {vm} = props
|
||||
|
||||
return <ButtonUi onClick={vm.onClick} >{vm.props.title}</ButtonUi>
|
||||
return <ButtonUi disabled={vm.props.isDisable} onClick={vm.onClick} >{vm.props.title}</ButtonUi>
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,10 +1,11 @@
|
||||
import ButtonVm from "@/app/components/button/button-vm";
|
||||
import { useServerAction } from "@/bootstrap/helpers/hooks/use-server-action";
|
||||
import useThrottle from "@/bootstrap/helpers/hooks/use-throttle";
|
||||
import BaseVM from "@/bootstrap/helpers/vm/base-vm";
|
||||
import { InvoiceParam } from "@/feature/core/invoice/domain/param/invoice-param";
|
||||
import createInvoiceUsecase from "@/feature/core/invoice/domain/usecase/create-invoice-usecase";
|
||||
import { faker } from "@faker-js/faker";
|
||||
|
||||
import { useRouter } from "next/navigation";
|
||||
export default class CreateRandomInvoiceButtonVM extends BaseVM<ButtonVm> {
|
||||
private createInvoice: typeof createInvoiceUsecase
|
||||
|
||||
@ -14,16 +15,20 @@ export default class CreateRandomInvoiceButtonVM extends BaseVM<ButtonVm> {
|
||||
}
|
||||
|
||||
useVM(): ButtonVm {
|
||||
const throttledOnClick = useThrottle(this.onClickHandler.bind(this), 5000)
|
||||
const router = useRouter()
|
||||
const [action, isPending] = useServerAction(() => this.onClickHandler(router.refresh))
|
||||
const throttledOnClick = useThrottle(action, 5000)
|
||||
|
||||
return {
|
||||
props: {
|
||||
title: "Create Random Invoice"
|
||||
title: isPending ? "Loading" : "Create Random Invoice",
|
||||
isDisable: isPending ? true : false
|
||||
},
|
||||
onClick: throttledOnClick
|
||||
onClick: throttledOnClick.bind(this)
|
||||
}
|
||||
}
|
||||
|
||||
onClickHandler() {
|
||||
async onClickHandler(refreshPage: () => void) {
|
||||
const fakedParams: InvoiceParam = {
|
||||
amount: faker.number.int({
|
||||
min: 1,
|
||||
@ -31,6 +36,7 @@ export default class CreateRandomInvoiceButtonVM extends BaseVM<ButtonVm> {
|
||||
}),
|
||||
status: "paid"
|
||||
}
|
||||
this.createInvoice(fakedParams)
|
||||
await this.createInvoice(fakedParams)
|
||||
refreshPage()
|
||||
}
|
||||
}
|
40
src/bootstrap/helpers/hooks/use-server-action.ts
Normal file
40
src/bootstrap/helpers/hooks/use-server-action.ts
Normal file
@ -0,0 +1,40 @@
|
||||
import { useState, useEffect, useTransition, useRef } from 'react';
|
||||
|
||||
/**
|
||||
*
|
||||
* @param action Main server action to run
|
||||
* @param onFinished Callback to run after action
|
||||
* @returns transitioned action to run and is pending variable
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export const useServerAction = <P extends any[], R>(
|
||||
action: (...args: P) => Promise<R>,
|
||||
onFinished?: (_: R | undefined) => void,
|
||||
): [(...args: P) => Promise<R | undefined>, boolean] => {
|
||||
const [isPending, startTransition] = useTransition();
|
||||
const [result, setResult] = useState<R>();
|
||||
const [finished, setFinished] = useState(false);
|
||||
const resolver = useRef<(value?: R | PromiseLike<R>) => void>();
|
||||
|
||||
useEffect(() => {
|
||||
if (!finished) return;
|
||||
|
||||
if (onFinished) onFinished(result);
|
||||
resolver.current?.(result);
|
||||
}, [result, finished, onFinished]);
|
||||
|
||||
const runAction = async (...args: P): Promise<R | undefined> => {
|
||||
startTransition(() => {
|
||||
action(...args).then((data) => {
|
||||
setResult(data);
|
||||
setFinished(true);
|
||||
});
|
||||
});
|
||||
|
||||
return new Promise((resolve) => {
|
||||
resolver.current = resolve;
|
||||
});
|
||||
};
|
||||
|
||||
return [runAction, isPending];
|
||||
};
|
@ -16,7 +16,7 @@ export default class InvoiceDbRepo implements InvoiceRepo {
|
||||
async createInvoice(params: InvoiceParam): Promise<string> {
|
||||
const firstCustomerIdDb = await sql`SELECT
|
||||
id FROM customers
|
||||
ORDER BY id ASC
|
||||
ORDER BY id DESC
|
||||
LIMIT 1
|
||||
`
|
||||
const customerId = firstCustomerIdDb.at(0)?.id
|
||||
|
@ -3,8 +3,6 @@ import serverDi from "@/feature/common/server-di";
|
||||
import InvoiceRepo, { invoiceRepoKey } from "@/feature/core/invoice/domain/i-repo/invoice-repo";
|
||||
import { InvoiceParam, invoiceSchema } from "@/feature/core/invoice/domain/param/invoice-param";
|
||||
import { invoiceModuleKey } from "@/feature/core/invoice/invoice-module-key";
|
||||
import { revalidatePath } from "next/cache";
|
||||
import { redirect } from "next/navigation";
|
||||
|
||||
export default async function createInvoiceUsecase(params: InvoiceParam): Promise<string | {errorMessage: string}> {
|
||||
const isParamsValid = invoiceSchema.safeParse(params)
|
||||
@ -16,7 +14,6 @@ export default async function createInvoiceUsecase(params: InvoiceParam): Promis
|
||||
}
|
||||
const repo = serverDi(invoiceModuleKey).resolve<InvoiceRepo>(invoiceRepoKey)
|
||||
|
||||
revalidatePath("/dashboard")
|
||||
return repo.createInvoice(params)
|
||||
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user