NOTE: Polkassembly doesn't do code highlighting, so a much easier to read version can be found here instead.
This proposal seeks continued funding for the development and refinement of the Reactive DOT library, building upon the progress made in proposal #812.
Reactive DOT is a library designed to:
The initial phase successfully achieved all outlined goals and stretch objectives, significantly enhancing the development experience for Substrate DApps.
The library was developed to work with Polkadot-API instead of Polkadot.js, achieving the following main goals:
Leveraging the extremely well-built and lean core provided by Polkadot-API, multiple stretch goals were achieved within the first development phase:
useAccounts
, useWallets
, and useConnectedWallets
.BigInt
and Number
.Reactive DOT now provides all the essential building blocks for developing a Substrate DApp, with walkthroughs guiding developers on:
This is achieved with complete type safety, autocompletion from TypeScript, centralised async/error handling (set once and forget), and behind-the-scenes caching, de-duplication, and persistence management.
The following tutorial demonstrates how quickly a simple DApp can be set up. The DApp will:
yarn add @reactive-dot/react dot-connect polkadot-api
npx papi add dot -n polkadot
npx papi
import type { polkadot } from "@polkadot-api/descriptors";
declare module "@reactive-dot/core" {
export interface Chains {
polkadot: typeof polkadot;
}
}
const config: Config = {
chains: {
polkadot: {
descriptor: polkadot,
provider: WebSocketProvider("wss://polkadot-rpc.publicnode.com"),
},
},
wallets: [
new InjectedWalletAggregator(),
new WalletConnect({
projectId: "WALLET_CONNECT_PROJECT_ID",
providerOptions: {
metadata: {
name: "APP_NAME",
description: "APP_DESCRIPTION",
url: "APP_URL",
icons: ["APP_ICON"],
},
},
chainIds: ["polkadot:91b171bb158e2d3848fa23a9f1c25182"],
}),
],
};
export default config;
// Register dot-connect custom elements and configure supported wallets
// this is the companion UI library for managing wallet connections
registerDotConnect({
wallets: config.wallets,
});
function App() {
return (
<ReDotProvider config={config}>
<ReDotChainProvider chainId="polkadot">
<ErrorBoundary
fallbackRender={() => (
<article>
<p>Sorry, something went wrong!</p>
</article>
)}
>
<Suspense fallback={<progress />}>
<DApp />
</Suspense>
</ErrorBoundary>
</ReDotChainProvider>
</ReDotProvider>
);
}
function FreeBalance(props: { address: string }) {
const chainSpec = useChainSpecData();
const {
data: { free },
} = useLazyLoadQuery((builder) =>
builder.readStorage("System", "Account", [props.address]),
);
const freeBalance = new DenominatedNumber(
free,
chainSpec.properties.tokenDecimals,
chainSpec.properties.tokenSymbol,
);
return (
<div>
{props.address}: {freeBalance.toLocaleString()}
</div>
);
}
function Remark() {
const accounts = useAccounts();
const [selectedAccountIndex, setSelectedAccountIndex] = useState(0);
const selectedAccount = accounts.at(selectedAccountIndex);
const [remarkState, remark] = useMutation(
(tx) =>
tx.System.remark({ remark: Binary.fromText("Hello from reactive-dot!") }),
{ signer: selectedAccount.signer },
);
return (
<section>
<select
onChange={(event) =>
setSelectedAccountIndex(Number(event.target.value))
}
>
{accounts.map((account, index) => (
<option key={index} value={index}>
{account.address}
</option>
))}
</select>
<button onClick={() => remark()} disabled={remarkState === PENDING}>
Hello{remarkState === PENDING ? "..." : ""}
</button>
</section>
);
}
function DApp() {
const accounts = useAccounts();
return (
<div>
{/* Button for managing wallet connection from `dot-connect` */}
{/* it will pop up a modal for you to connect and disconnect wallets */}
<dc-connection-button />
{accounts.map((account, index) => (
<FreeBalance key={index} address={account.address} />
))}
{accounts.length > 0 and <Remark />}
</div>
);
}
Achieving the same outcome without this library would require substantially more code, while being much harder to maintain and reason with.
This proposal requests continued funding for Reactive DOT development, focusing on:
React 19 will introduce the use
API, enabling components to conditionally consume promise results. Reactive DOT currently lazy loads queries (fetch-on-render
) and will need to expose a promise-centric API to fully utilise React 19 features.
The library's goal is to support building Substrate front-ends beyond just React. Based on the latest State of JavaScript figures, the next frameworks for support, in order of importance, are:
Existing work has accounted for this by splitting primitives and functionalities into @reactive-dot/core
and @reactive-dot/react
to prepare for future integrations.
Server-side rendering (SSR) is increasingly important, with applications like Polkassembly and SubSquare both built using Next.js. However, SSR traditionally presents challenges for Substrate DApps, which rely heavily on live WebSocket data. Potential strategies include:
render-as-you-fetch
) but static values.fetch-on-render
pattern.A hybrid approach is proposed, fetching initial values server-side to hydrate the client cache, which then updates with live chain data:
async function ServerComponent() {
const preparedQuery = await prepareQuery((builder) =>
builder
.readStorage("SomePallet", "SomeStorage")
.readStorage("SomeOtherPallet", "SomeOtherStorage"),
);
return <ClientComponent query={preparedQuery} />;
}
"use client";
function ClientComponent(props: { query: PreparedQuery }) {
const resultThatIsInstantAndLive = usePreparedQuery(props.query);
// ...
}
Note: The above example illustrates the internal workings of the idea. Ideally, a logic split isn't explicitly required by developers.
In addition to the concrete goals above, feedback is welcomed to determine the focus of future work.
Fuelled by positive feedback and a clearly defined path forward, I am confident that we can not only match but surpass the frontend developer experience of other ecosystems (i.e. EVM), setting a new industry standard.
The initial proposal requested funding for 2 months, which proved inadequate as a buffer due to the proposal process itself taking 1-2 months to conclude. During this time, I continued working without knowing the outcome. Based on that experience, I am now requesting a budget for 4 months, enabling me to fully dedicate my attention to research and development.