A plug and play, customizable way to manage user transaction status on your dapp
- β Out-of-the-box status notifications on top of @zag-js/toast (totally optional)
- π Easily customizable, or create your own with simple react hooks
- π₯ Using
viem
andwagmi
- β¨ Framework agnostic core at
@pcnv/txs-core
- π¦ Tiny tiny, less than
3 kB gzipped
pnpm add @pcnv/txs-react viem wagmi @zag-js/react @zag-js/toast
# zag is not required if you want to create your own notification components
pnpm add @pcnv/txs-react viem wagmi
Check the examples folder for a complete implementation
import {
createTransactionsStore,
ToastsViewport,
TransactionsStoreProvider,
TransactionStatusToastProps,
} from '@pcnv/txs-react'
// import a builtin toast component or create your own
import { ClassicToast } from '@pcnv/txs-react/toasts/ClassicToast'
import '@pcnv/txs-react/toasts/ClassicToast/styles.css'
const transactionsStore = createTransactionsStore()
...
// Add the provider to your app
// make sure its a children of WagmiConfig
<WagmiConfig client={...}>
<TransactionsStoreProvider store={transactionsStore}>
<ToastsViewport
TransactionStatusComponent={ClassicToast}
placement="top-end"
/>
...
And in your component
import { usePrepareContractWrite, useContractWrite } from 'wagmi'
import { useAddRecentTransaction } from '@pcnv/txs-react'
...
const addTransaction = useAddRecentTransaction()
const { writeContract } = useWriteContract()
...
writeContract(
{
address: ...,
abi: ...,
functionName: ...,
},
{
onSuccess: (hash) => {
addTransaction({
hash,
meta: {
// more on meta below
description: `Wrapping ${amount} ETH`,
},
})
},
},
)
returns all transactions stored for the connected user in the connected chain
const recentTransactions = useRecentTransactions()
It also accepts a selector
// this component will only rerender when a new transaction is set as completed
const completedTransactions = useRecentTransactions((txs) =>
txs.filter(({ status }) => status === 'completed'),
)
Adds a transaction to be tracked, to the connected user/chain
const addTransaction = useAddRecentTransaction()
...
addTransaction({
hash: '0x your transaciton hash',
meta: {
// metadata about the transaciton, description, type etc, more on the meta field below
}
})
Removes a connected user transaction by hash
const removeTransaction = useRemoveRecentTransaction()
...
removeTransaction(hash)
Clears all transactions stored on the current connected user/chain
const clearTransactions = useClearRecentTransactions()
...
clearTransactions()
Listens for an event from the store to execute a callback
useTransactionsStoreEvent(
'added',
useCallback((tx) => {
// a new transaction was added, do something
}, []),
)
Supported events are added
, updated
, removed
, cleared
, mounted
Useful if you are building your own notification solution
Maybe you want to display a confirmation dialog on transaction confimed. that could look something like this
useTransactionsStoreEvent(
'updated',
useCallback(
(tx) => {
if (tx.status === 'confirmed') displayTxConfirmedDialog(tx)
},
[displayTxConfirmedDialog],
),
)
Both detect prefers-color-scheme
and style light
/dark
accordingly, you can force by passing a colorScheme
prop, default is system
import { EmojiToast } from '@pcnv/txs-react/toasts/EmojiToast'
import '@pcnv/txs-react/toasts/EmojiToast/styles.css'
import { ClassicToast } from '@pcnv/txs-react/toasts/ClassicToast'
import '@pcnv/txs-react/toasts/ClassicToast/styles.css'
The following can be configured thru props on ToastsViewport
showPendingOnReopen
: should pop up the pending notification when the user closes and opens the app again while still pending? defaults to truestaleTime
: if the user closed the app without a status update, for how long it's still relevant to display the update on reopenstuckTime
: transactions are considered stuck after 30min without a confirmation/rejection
The meta
field accepts anything serializable really,
for example, instead of a single description
you may want to have custom description for pending
, completed
and failed
Here's an example of how that could work
import {
StoredTransaction,
ToastsViewport,
TransactionStatusToastProps,
TypedUseAddRecentTransaction,
TypedUseRecentTransactions,
} from '@pcnv/txs-react'
// add your custom fields to the global namespace Txs.Meta
declare global {
namespace Txs {
export interface Meta {
pending: string
success: string
reverted: string
}
}
}
const MyCustomNotification = (props: TransactionStatusToastProps<TransactionMeta>) => {
const tx = props.transaction
return <EmojiToast {...props} description={tx.meta[tx.status]} />
}
...
<ToastsViewport TransactionStatusComponent={MyCustomNotification} />
...
// and in you component ts will enforce this usage
const addTransaction = useAddRecentTransaction()
...
addTransaction({
hash: tx.hash,
meta: {
pending: '...',
completed: '...',
failed: '...',
},
})
Note Beware that everything included as
meta
will be saved to LocalStorage
This time only some properties are saved to localstorage based on the transaction type
type TransactionType = { type: 'approve'; amount: string; token: string } // | { ...more types }
declare global {
namespace Txs {
export interface Meta extends TransactionType {}
}
}
type TransactionMetaToStatusLabel = {
[Meta in TransactionType as Meta['type']]: (
meta: Omit<Meta, 'type'>,
) => Record<StoredTransaction['status'], string>
}
const typeToStatusDescription = {
approve: ({ amount, token }) => ({
pending: `Approving ${amount} ${token}`,
confirmed: `Successfully approved ${amount} ${token}`,
failed: `Failed to approve ${amount} ${token}`,
}),
} satisfies TransactionMetaToStatusLabel
const MyCustomNotification = (props: TransactionStatusToastProps<TransactionType>) => {
const { meta, status } = props.transaction
const description = typeToStatusDescription[meta.type](meta)[status]
return <EmojiToast {...props} description={description} />
}
...
const addTransaction = useAddRecentTransaction()
addTransaction({
hash: tx.hash,
meta: {
type: 'approve', // typescript can auto suggest all available types and their required properties
amount: 1,
token: 'CNV'
},
})
Check Zagjs Docs
Check how's it being used it the concave repo