Hi all
. I’m Tien, author of ReactiveDOT. I’ve just finished a set of async primitives that make Polkadot front-ends feel like they were built for React 19 from day one. Of which I would love to share with you all
.
What’s new
Suspense-first async, with an escape hatch when you need it.
ReactiveDOT hooks suspend by default and plug straight into React’s Suspense. When you need explicit control, pass { use: false } to get a stable, stateful Promise that works with React 19’s use(), plus ergonomic patterns for non-suspending UIs and parallel composition.
Why this matters
- Less boilerplate: Loading and error UI is handled where it belongs: Suspense and Error Boundaries.
- Full React 19 compatibility: Opt out to promises and have maximum control over async handling.
- Positioning: To my knowledge, ReactiveDOT & by extension Polkadot now offers the most complete React 19–native async integrations in a web3 FE library, covering default Suspense,
use()-ready promises, non-suspending flows & parallel composition.
How it works (quick tour)
Suspense mode (default)
Least amount of code; hooks suspend while data loads.
import { useSpendableBalance } from "@reactive-dot/react";
import { Suspense } from "react";
function UserBalance({ address }: { address: string }) {
const balance = useSpendableBalance(address); // suspends by default
return <div>Balance: {balance.toLocaleString()}</div>;
}
export function App() {
return (
<Suspense fallback={<div>Loading balance...</div>}>
<UserBalance address={ADDRESS} />
</Suspense>
);
}
Promise mode + use()
Opt out with { use: false } to get a stable, stateful Promise, then suspend exactly where you call use().
import { useSpendableBalance } from "@reactive-dot/react";
import { use, Suspense } from "react";
function UserBalance({ address }: { address: string }) {
const balancePromise = useSpendableBalance(address, { use: false });
const balance = use(balancePromise); // suspends at call site
return <div>Balance: {balance.toLocaleString()}</div>;
}
Non-suspending UIs with usePromiseState
Familiar pattern for non-suspend ready UI.
import { pending } from "@reactive-dot/core";
import { useSpendableBalance, usePromiseState } from "@reactive-dot/react";
function SmoothBalance({ address }: { address: string }) {
const balance = usePromiseState(
useSpendableBalance(address, { use: false }),
(prev) => prev ?? undefined,
);
if (balance === undefined) {
return <div>Loading balance...</div>;
}
return <div>Balance: {balance.toLocaleString()}</div>;
}
Parallel composition with usePromises
Kick off multiple reads together and render when all are ready.
import {
useSpendableBalance,
useBlock,
useLazyLoadQuery,
usePromises,
} from "@reactive-dot/react";
function UserProfile({ address }: { address: string }) {
const [balance, block, identity] = usePromises([
useSpendableBalance(address, { use: false }),
useBlock({ use: false }),
useLazyLoadQuery((q) => q.storage("Identity", "IdentityOf", [address]), {
use: false,
}),
]);
return (
<div>
<p>Balance: {balance.toLocaleString()}</p>
<p>Block: #{block.number.toLocaleString()}</p>
<p>Identity: {identity?.info.display.asText() ?? "None"}</p>
</div>
);
}
Declarative awaiting with <Await/>
A render-props helper that works alongside Suspense.
import { useSpendableBalance, Await } from "@reactive-dot/react";
import { Suspense } from "react";
function UserBalance({ address }: { address: string }) {
const balancePromise = useSpendableBalance(address, { use: false });
return (
<Suspense fallback={<div>Loading…</div>}>
<Await promise={balancePromise}>
{(balance) => <div>{balance.toLocaleString()}</div>}
</Await>
</Suspense>
);
}
Choosing a pattern
- Default (Suspense): simplest for most components.
use()with promises: fine-grained control, still using Suspense.usePromiseState: non-suspending UIs, reusable hooks, stale-while-refresh.usePromises: parallel reads.<Await/>: declarative composition where a render prop reads better.
Error handling
All patterns work with Error Boundaries. Place boundaries where you want failures contained. For example, use a top-level boundary to catch everything, or per section so one panel can fail without taking the page down.
Final remarks
I’m hopeful this guide and the examples show how straightforward and fast it can be to build Polkadot UIs with React 19–native concurrency. I’m keen to hear how it feels in your apps and where it can be smoother.
Links
- Docs: https://reactivedot.dev/
- GitHub: GitHub - buffed-labs/reactive-dot: A reactive library for building Substrate front-ends
Thanks for reading ![]()