Create a frontend for your dapp using React
This section elaborates on how the frontends from your dapp can interact with the example contracts and access chain data, and connect to a freighter wallet. This will be illustrated by utilizing libraries provided by @soroban-react
, a simple, powerful framework for building modern Dapps using React. @soroban-react
was created and is maintained by an amazing member of the community!
This guide will demonstrate how an example crowdfund dapp frontend was developed with React. While much of the code is specific to this project, the principles demonstrated should be educational enough to get you started.
Below is a list of the libraries used throughout the frontend code and their respective imports:
import { SorobanReactProvider } from "@soroban-react/core";
import { testnet, sandbox, standalone } from "@soroban-react/chains";
import { freighter } from "@soroban-react/freighter";
import { ChainMetadata, Connector } from "@soroban-react/types";
import type {
WalletChain,
ChainMetadata,
ChainName,
} from "@soroban-react/types";
import { useSorobanReact } from "@soroban-react/core";
These imports include SorobanReactProvider
from @soroban-react/core
, which is a context provider used to pass the SorobanReact instance to other components. You also import several types such as WalletChain
, ChainMetadata
, and ChainName
, which help to maintain type safety within our application.
React Components and Prop Passing
React thrives on its component-based architecture. Components are reusable pieces of code that return a React element to be rendered on the page. A typical React application consists of multiple components working harmoniously to create a dynamic user interface.
Let's look at a component from the the example crowdfund dapp, the MintButton
component:
function MintButton({
account,
decimals,
symbol,
}: {
account: string;
decimals: number;
symbol: string;
}) {
const [isSubmitting, setSubmitting] = useState(false);
const { activeChain, server } = useNetwork();
const networkPassphrase = activeChain?.networkPassphrase ?? "";
const { sendTransaction } = useSendTransaction();
const amount = BigNumber(100);
return <Button props omitted here />;
}
This functional component takes three properties as arguments: account
, decimals
, and symbol
. It demonstrates the concept of prop passing, a way to pass data from parent to child components in React. The onComplete
prop even allows you to pass functions to your copmonents as props. We also see React's useState
hook for local state management, a method to preserve values between function calls.
State Management and Hooks
State management is another core concept of React, allowing components to create and manage their own data. The useState
hook is a feature introduced in React 16.8 that allows functional components to have their own state.
In the MintButton
component, the useState
hook is used to manage the isSubmitting
state:
const [isSubmitting, setSubmitting] = useState(false);
The useState
hook returns a pair of values: the current state and a function that updates it. In this case, the isSubmitting
state is initialized to false
and the setSubmitting
function is used to update it. React also allows for the creation of custom hooks, like useNetwork
and useSendTransaction
, for encapsulating and reusing stateful logic across multiple components.
Custom Hooks
React hooks are functions that let you “hook into” React state and lifecycle features from functional components. Custom hooks allow you to encapsulate complex logic and make it reusable across components. Let's take a look at useNetwork
and useSendTransaction
, two custom hooks used in the example crowdfund dapp.
The useNetwork
hook is utilized to interact with the blockchain network, and the useSendTransaction
hook is used to dispatch transactions. These hooks abstract away complex logic, making it easier to read and understand the main component code.
Here's how you use these hooks in the MintButton
component:
const { activeChain, server } = useNetwork();
const networkPassphrase = activeChain?.networkPassphrase ?? "";
const { sendTransaction } = useSendTransaction();
useNetwork
provides the active chain and the server, and useSendTransaction
gives us the sendTransaction
method, which you'll later use to mint tokens. This way, you can keep the component focused on rendering and event handling logic, making it easier to test and maintain.
Asynchronous Processing and Robust Error Handling
When dealing with operations that might take an unpredictable amount of time, like network requests or, in our case, minting tokens on the blockchain, React's support for asynchronous operations is crucial. This allows the execution of the rest of the code without being blocked by these operations.
Let's dive into the code snippet that handles the asynchronous minting process:
try {
console.log("Minting the token...");
const paymentResult = await sendTransaction(
new SorobanClient.TransactionBuilder(adminSource, {
networkPassphrase,
fee: "1000",
})
.setTimeout(10)
.addOperation(
SorobanClient.Operation.payment({
destination: walletSource.accountId(),
asset: new SorobanClient.Asset(symbol, Constants.TokenAdmin),
amount: amount.toString(),
}),
)
.build(),
{
timeout: 10 * 1000,
skipAddingFootprint: true,
secretKey: Constants.TokenAdminSecretKey,
sorobanContext,
},
);
console.debug(paymentResult);
sorobanContext.connect();
} catch (err) {
console.log("Error while minting the token: ", err);
console.error(err);
}
This block is where the actual token minting occurs. It's wrapped in a try-catch
block, ensuring that any errors during the minting process are caught and handled appropriately, preventing the application from crashing and giving you a chance to provide feedback to the user.
The await
keyword pauses the execution of the function until the promise returned by sendTransaction
resolves. sendTransaction
is a function obtained from our useSendTransaction
hook, and it builds and sends a payment operation to the Stellar network.
The sendTransaction
method accepts two arguments: a TransactionBuilder
instance and an options object. The TransactionBuilder
sets up the details of the transaction, such as the source account, network passphrase, transaction fee, and operations to be performed—in this case, a payment operation.
If the transaction is successful, paymentResult
contains the result, which you log for debugging purposes. If an error occurs during the transaction, the function throws an error, which you catch and log.
Conclusion
React offers a host of high-level concepts that can drastically improve your web development process. By understanding and utilizing these concepts—such as components, prop passing, state management, asynchronous operations, and error handling—you can create scalable, maintainable, and efficient applications.
Remember, the key to mastering React is practice. So, keep building and experimenting!
Guides in this category:
📄️ Use Docker to build and run dapps
Understand Docker and use it to build applications
📄️ Comprehensive frontend guide for Stellar dapps
Learn how to build functional frontend interfaces for Stellar dapps using React, Tailwind CSS, and the Stellar SDK.
📄️ Initialize a dapp using scripts
Set up initialization correctly to ensure seamless setup for your dapp
📄️ Create a frontend for your dapp using React
Connect dapp frontends to contracts and Freighter wallet using @soroban-react
📄️ Develop contract initialization frontend templates
Understand, find, and create your own frontend templates for use with Stellar CLI's `stellar contract init` command
📄️ Implement state archival in dapps
Learn how to implement state archival in your dapp
📄️ Work with contract specs in Java, Python, and PHP
A guide to understanding and interacting with Soroban smart contracts in different programming languages