Skip to content

Commit eb26cbd

Browse files
committed
feat: Add useTransactionStatus and example
1 parent f636eb4 commit eb26cbd

File tree

4 files changed

+209
-0
lines changed

4 files changed

+209
-0
lines changed

examples/react/pages/index.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ export default function Index() {
1212
<Example name="use-wallet-discovery" />
1313
<Example name="use-mutation" />
1414
<Example name="use-account" />
15+
<Example name="use-transaction-status" />
1516
</ul>
1617
</>
1718
);
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
import {
2+
createClient,
3+
FlowProvider,
4+
networks,
5+
useTransactionStatus,
6+
} from "@maggo/use-flow";
7+
import { block, decode, getEventsAtBlockIds, send } from "@onflow/fcl";
8+
import { useRef, useState } from "react";
9+
10+
const client = createClient({
11+
fclConfig: {
12+
...networks.mainnet,
13+
},
14+
});
15+
16+
export default function UseTransactionStatus() {
17+
return (
18+
<FlowProvider client={client}>
19+
<h1>useTransactionStatus Example</h1>
20+
<Subscription />
21+
</FlowProvider>
22+
);
23+
}
24+
25+
function Subscription() {
26+
const formRef = useRef<HTMLFormElement>(null);
27+
const [query, setQuery] = useState<string>();
28+
29+
const { status, isLoading } = useTransactionStatus({
30+
id: query,
31+
});
32+
33+
async function getLatestTx() {
34+
const blockData = await block();
35+
36+
const events = await send([
37+
getEventsAtBlockIds("A.f919ee77447b7497.FlowFees.FeesDeducted", [
38+
blockData.id,
39+
]),
40+
]).then(decode);
41+
42+
if (!events.length) return;
43+
44+
const txId = events[events.length - 1].transactionId;
45+
46+
if (formRef.current) {
47+
const el = formRef.current.elements.namedItem(
48+
"query"
49+
) as HTMLInputElement;
50+
51+
el.value = txId;
52+
}
53+
54+
setQuery(txId);
55+
}
56+
57+
const Header = (
58+
<>
59+
<form
60+
ref={formRef}
61+
style={{ display: "flex", gap: ".5rem", alignItems: "center" }}
62+
onSubmit={(e) => {
63+
e.preventDefault();
64+
const el = e.currentTarget.elements.namedItem(
65+
"query"
66+
) as HTMLInputElement;
67+
setQuery(el.value);
68+
}}
69+
>
70+
<input type="search" name="query" defaultValue={query} />
71+
<button type="submit">Submit</button>
72+
{" | "}
73+
<button type="button" onClick={() => getLatestTx()}>
74+
Get latest tx
75+
</button>
76+
</form>
77+
<br />
78+
</>
79+
);
80+
81+
if (isLoading) {
82+
return (
83+
<>
84+
{Header}
85+
<p>Loading…</p>
86+
</>
87+
);
88+
}
89+
90+
if (!status) {
91+
return (
92+
<>
93+
{Header}
94+
<p>No transaction selected…</p>
95+
</>
96+
);
97+
}
98+
99+
return (
100+
<>
101+
{Header}
102+
<p>
103+
Transaction Status:{" "}
104+
<code>
105+
{status?.status} = {status?.statusString}
106+
</code>
107+
</p>
108+
<details>
109+
<summary>Raw Result</summary>
110+
<pre style={{ overflowX: "auto" }}>
111+
{JSON.stringify(status, null, 2)}
112+
</pre>
113+
</details>
114+
</>
115+
);
116+
}

packages/core/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,5 @@ export { useBlock } from "./useBlock";
88
export { useEventSubscription } from "./useEventSubscription";
99
export { useMutation } from "./useMutation";
1010
export { useScript } from "./useScript";
11+
export { useTransactionStatus } from "./useTransactionStatus";
1112
export { useWalletDiscovery } from "./useWalletDiscovery";
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
import { tx } from "@onflow/fcl";
2+
import { useEffect, useState } from "react";
3+
4+
export enum TransactionStatus {
5+
/** Transaction unknown */
6+
Unknown = 0,
7+
/** Transaction Pending - Awaiting Finalization */
8+
Pending = 1,
9+
/** Transaction Finalized - Awaiting Execution */
10+
Finalized = 2,
11+
/** Transaction Executed - Awaiting Sealing */
12+
Executed = 3,
13+
/** Transaction Sealed - Transaction Complete. At this point the transaction result has been committed to the blockchain. */
14+
Sealed = 4,
15+
/** Transaction Expired */
16+
Expired = 5,
17+
}
18+
19+
interface Event {
20+
type: string;
21+
transactionId: string;
22+
transactionIndex: number;
23+
eventIndex: number;
24+
data: Record<string, any>;
25+
}
26+
27+
interface TransactionResult {
28+
blockId: string;
29+
errorMessage: string;
30+
events: Event[];
31+
status: TransactionStatus;
32+
statusCode: 0 | 1;
33+
statusString: "SEALED";
34+
}
35+
36+
interface UseTransactionStatusProps {
37+
/** A valid transaction id. */
38+
id?: string;
39+
/** Provides the transaction once status `2` is returned. */
40+
onceFinalized?: (txStatus: TransactionResult) => void;
41+
/** Provides the transaction once status `3` is returned. */
42+
onceExecuted?: (txStatus: TransactionResult) => void;
43+
/** Provides the transaction once status `4` is returned. */
44+
onceSealed?: (txStatus: TransactionResult) => void;
45+
}
46+
47+
/**
48+
* This hook enables you to get status updates (via polling) and the finalized result of a transaction once available.
49+
*
50+
* The status is polled every 2.5 seconds.
51+
*
52+
* @TODO Refactor to custom getTransactionStatus calls since FCL tx() has no error handling and isn't cancelable.
53+
* @TODO Investigate transaction statuses that are just empty strings
54+
* @see {@link https://developers.flow.com/tools/fcl-js/reference/api#tx}
55+
*/
56+
export function useTransactionStatus({
57+
id,
58+
onceFinalized,
59+
onceExecuted,
60+
onceSealed,
61+
}: UseTransactionStatusProps) {
62+
const [isLoading, setIsLoading] = useState(true);
63+
const [status, setStatus] = useState<TransactionResult>();
64+
65+
useEffect(() => {
66+
if (!id) {
67+
setIsLoading(false);
68+
return;
69+
}
70+
71+
setIsLoading(true);
72+
73+
const txHandler = tx(id);
74+
txHandler.subscribe((txStatus: TransactionResult) => {
75+
setIsLoading(false);
76+
setStatus(txStatus);
77+
});
78+
79+
txHandler.onceFinalized(onceFinalized);
80+
txHandler.onceExecuted(onceExecuted);
81+
txHandler.onceSealed(onceSealed);
82+
}, [id]);
83+
84+
return {
85+
isLoading,
86+
status,
87+
isFinalized: status?.status === TransactionStatus.Finalized,
88+
isExecuted: status?.status === TransactionStatus.Executed,
89+
isSealed: status?.status === TransactionStatus.Sealed,
90+
};
91+
}

0 commit comments

Comments
 (0)