Skip to main content

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!

info

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!