reactive-dot
: A reactive library for building Substrate front-endI'm Tien and was a lead front-end developer working in the Polkadot space for the past 1.5 years.
Now I'm going independent and I'd like to solve some developer experience issues I encountered along the way.
Prior to my involvement with Polkadot, I've worked on various projects within other ecosystems, namely Ethereum & Cosmos.
This library came about based on my experience building out front-ends with Polkadot.js, and comparing it with the developer experience available in Ethereum, realizing the same wasn't available for Polkadot.
This proposal outlines the creation of reactive-dot
, a comprehensive React library designed to simplify and streamline the integration of Polkadot network functionalities into React applications. Inspired by the popular Wagmi library for Ethereum, reactive-dot
aims to provide developers with an easy-to-use, modular, and flexible toolkit for interacting with the Polkadot ecosystem.
Easy chain selection via React context.
const Root = () => (
<ReDotProvider
config={{
providers: {
"0x91b171bb158e2d3848fa23a9f1c25182fb8e20313b2c1eb49219da7a70ce90c3":
new WsProvider("wss://apps-rpc.polkadot.io"),
"0xb0a8d493285c2df73290dfb7e61f870f17b41801197a149ca93654499ea3dafe":
new WsProvider("wss://kusama-rpc.polkadot.io"),
},
}}
>
<ReDotChainProvider genesisHash="0x91b171bb158e2d3848fa23a9f1c25182fb8e20313b2c1eb49219da7a70ce90c3">
<App />
</ReDotChainProvider>
<ReDotChainProvider genesisHash="0xb0a8d493285c2df73290dfb7e61f870f17b41801197a149ca93654499ea3dafe">
<App />
</ReDotChainProvider>
</ReDotProvider>
);
Or via options override
const account = useQueryStorage("system", "account", [accountAddress], {
genesisHash:
"0xb0a8d493285c2df73290dfb7e61f870f17b41801197a149ca93654499ea3dafe",
});
Access and read data stored in the Substrate-based storage directly from your React components.
// Reading single value
// this value is live from chain and will be updated automatically
const totalIssuance = useQueryStorage("balances", "totalIssuance", []);
console.log("Total issuance:", totalIssuance.toHuman());
// Reading multiple values
const poolMetadatum = useQueryStorage(
"nominationPools",
"metadata",
[0, 1, 2, 3],
{
multi: true,
},
);
for (const poolMetadata of poolMetadatum) {
console.log("Pool name:", poolMetadata.toUtf8());
}
React suspense are first class citizen for async & error handling.
const CurrentBlock = () => {
const currentBlock = useQueryStorage("system", "number", []);
return <p>Current block: {currentBlock}</p>;
};
const App = () => (
<ErrorBoundary fallback="Error fetching block">
<Suspense fallback="Loading block...">
<CurrentBlock />
</Suspense>
</ErrorBoundary>
);
Multiple reads of the same value throughout the application will only be fetched once, cached, and is kept up to date everywhere.
const myAccount = "SOME_ACCOUNT_ADDRESS";
const FreeBalance = () => {
// First invocation will initiate subscription via web socket
const account = useQueryStorage("system", "account", [myAccount]);
return <p>Free balance: {account.data.free.toHuman()}</p>;
};
const FrozenBalance = () => {
// Second invocation will only wait for and reuse value coming from the first invocation
const account = useQueryStorage("system", "account", [myAccount]);
return <p>Frozen balance: {account.data.frozen.toHuman()}</p>;
};
const ReservedBalance = () => {
// Third invocation will also only wait for and reuse value coming from the first invocation
const account = useQueryStorage("system", "account", [myAccount]);
return <p>Reserved balance: {account.data.reserved.toHuman()}</p>;
};
const App = () => (
<div>
{/* `useQueryStorage("system", "account", [myAccount])` will only be executed once & is kept up to date for all 3 components */}
<FreeBalance />
<FrozenBalance />
<ReservedBalance />
</div>
);
The library aim to provides strong TypeScript definition with 1-1 mapping to Substrate pallets definition.
The scope of this library can expand significantly based on community interest. Potential future features include:
U32
-> Number
, U256
-> BigInt
, etc)A working proof of concept showcasing the library can be found here.
The below code snippets perform the following tasks:
import { ApiPromise, WsProvider } from "@polkadot/api";
import type { u32 } from "@polkadot/types-codec";
import type { FrameSystemAccountInfo } from "@polkadot/types/lookup";
const MY_ACCOUNT = "SOME_ADDRESS";
const LOADING = new Symbol();
const App = () => {
const [api, setApi] = useState<ApiPromise | LOADING | Error>();
const [currentBlock, setCurrentBlock] = useState<u32 | LOADING | Error>();
const [account, setAccount] = useState<
FrameSystemAccountInfo | LOADING | Error
>();
useEffect(() => {
(async () => {
setApi(LOADING);
try {
const api = await ApiPromise.create({
provider: new WsProvider("wss://my.chain"),
});
setApi(api);
} catch (error) {
setApi(new Error("Unable to initialize ApiPromise", { cause: error }));
}
})();
}, []);
useEffect(() => {
if (api === LOADING || api instanceof Error) {
return;
}
const unsubscribePromise = (async () => {
setCurrentBlock(LOADING);
try {
return api.query.system.number((currentBlock) =>
setCurrentBlock(currentBlock),
);
} catch (error) {
setCurrentBlock(
new Error("Unable to get current block", { cause: error }),
);
}
})();
return () => {
unsubscribePromise.then((unsubscribe) => {
if (unsubscribe === undefined) {
return;
}
unsubscribe();
});
};
}, [api]);
useEffect(() => {
if (api === LOADING || api instanceof Error) {
return;
}
const unsubscribePromise = (async () => {
setAccount(LOADING);
try {
return api.query.system.account(MY_ACCOUNT, (account) =>
setAccount(account),
);
} catch (error) {
setAccount(new Error("Unable to get account", { cause: error }));
}
})();
return () => {
unsubscribePromise.then((unsubscribe) => {
if (unsubscribe === undefined) {
return;
}
unsubscribe();
});
};
}, [api]);
if (api === LOADING || currentBlock === LOADING || account === LOADING) {
return <p>Loading...</p>;
}
if (
api instanceof Error ||
currentBlock instanceof Error ||
account instanceof Error
) {
return <p>Sorry, something went wrong.</p>;
}
return (
<p>
Your account free balance is: {account.data.free.toHuman()} at block{" "}
{currentBlock.toNumber()}
</p>
);
};
reactive-dot
const MY_ACCOUNT = "SOME_ADDRESS";
const _Balance = () => {
const currentBlock = useQueryStorage("system", "number", []);
const account = useQueryStorage("system", "account", [MY_ACCOUNT]);
return (
<p>
Your account free balance is: {account.data.free.toHuman()} at block{" "}
{currentBlock.toNumber()}
</p>
);
};
const Balance = () => (
<ErrorBoundary fallback={<p>Sorry, something went wrong.</p>}>
<Suspense fallback={<p>Loading...</p>}>
<_Balance />
</Suspense>
</ErrorBoundary>
);
const App = () => (
<ReDotProvider
config={{
providers: {
[SOME_GENESIS_HASH]: new WsProvider("wss://my.chain"),
},
}}
>
<ReDotChainProvider genesisHash={SOME_GENESIS_HASH}>
<Balance />
</ReDotChainProvider>
</ReDotProvider>
);
Requested amount: 6,000 DOT
Estimated length of work: 8 weeks/~320 hours
Estimated rate: 18.75 DOT or ~139.20 USD per hour
The requested amount also covers the retrospective work from numerous experiments and research efforts that validated this idea and led to the development of the initial working proof of concept.
Of which version 1 will include React support for the capabilities outlined in the section before, excluding possible future goals
reactive-dot
aims to revolutionize the way developers interact with the Polkadot network by providing a robust, user-friendly, and feature-rich React library. By simplifying the development process and fostering a vibrant community, reactive-dot
will play a pivotal role in promoting the adoption and growth of the Polkadot ecosystem. We seek the support and funding from the treasury to bring this ambitious project to life.
reactive-dot
: A Reactive Library for Polkadot Front-End DevelopmentOverview: reactive-dot
is a React library designed to simplify the integration of Polkadot network functionalities into React applications. It aims to improve developer experience by reducing boilerplate code and complexity.
Objectives:
Key Features:
Future Goals: Potential features include wallet management, transaction submission, and support for multiple frameworks.
Demo: A proof of concept is available here.
Code Comparison: Demonstrates simplified code for connecting to the chain, reading block and account balance, and handling loading and error states.
Timeline & Budget: The project is estimated to take 8 weeks and requires 6,000 DOT. It includes research, core development, testing, documentation, and community feedback.
Threshold
@tien Thanks for the reply. Looking for a strict equivalent of wagmi totally makes sense given the traction it got.
If you ask me, I wouldn't make it too complex under the hood to start with (e.g with no ability to change between pjs and papi) and keep things as simple and maintainable as possible. The most important is to build a user base, which is IMHO the hard part.
I’m giving an AYE to Tien’s proposal. This proposal is a great step towards improving our developer tools and making Polkadot more accessible and user-friendly. By lowering the entry barrier for developers, reactive-dot can encourage more projects to build on Polkadot. This aligns with our goal of growing the ecosystem and attracting more users and developers. Tien has a proven track record as a lead front-end developer in the Polkadot space. His experience and prior work, like the Talisman Portal, demonstrate his capability to deliver high-quality tools. The proposal has a clear timeline and budget, ensuring transparency and accountability.
Thanks a lot for the proposal Tien. Seconding what William said, I think more tooling and easier to access libs, such as wagmi equivalents, are needed and welcome.
A context provider per chain and a state storage request hook with strong typing sound interesting, but I wonder if this is not too focused on the flaws of pjs/api, that papi will solve, while omitting the very important part of transaction submission.