Developing a Full-Stack Project on Stacks with Clarity Smart Contracts and Stacks.js Part III: Frontend
Tags: clarity, stacks, web3, react
Published: 2022-10-06 16:22:53


## Frontend

In the terminal from the root `gm` directory run:

```
cd frontend
```

```
npm install
```

Before we start writing code, let's take a quick look at the features we'll be using from Stacks.js.

[Stacks.js](https://www.hiro.so/stacks-js) is a full featured JavaScript library for dApps on Stacks.

**@stacks/connect** allows devs to connect web applications to Stacks wallet browser extensions.

**@stacks/transactions** allows for interactions with the smart contract functions and post conditions.

**@stacks/network** is a network and API library for working with Stacks blockchain nodes.

In your `index.js` file, replace the boilerplate code with:

```
import Head from "next/head";
import ConnectWallet from "../components/ConnectWallet";
import styles from "../styles/Home.module.css";

export default function Home() {
  return (
    <div className={styles.container}>
      <Head>
        <title>gm</title>
        <meta name="description" content="Generated by create next app" />
        <link rel="icon" href="/favicon.ico" />
      </Head>

      <main className={styles.main}>
        <h1 className={styles.title}>gm</h1>

        <div className={styles.components}>
          {/* ConnectWallet file: `../components/ConnectWallet.js` */}
          <ConnectWallet />
        </div>
      </main>
    </div>
  );
}
```

Now run `npm start` and navigate to `localhost:3000`.


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/u9re486an6fr89lfjq9g.png)

We have a super simple landing page with [Hiro wallet](https://wallet.hiro.so/) login. We won't focus on styling for this example.

Looking at the code you'll see the `ConnectWallet` component has been created already. This is included as part of the Stacks.js Starters. 

Let's go into the `components` directory. Here we see two files:
- `ConnectWallet.js`
- `ContractCallVote.js`

If you're curious about `ContractCallVote` you can add the component to `index.js` and try it out. For this example we won't be using it.

`ConnectWallet.js` contains an `authenticate` function that creates a `userSession`. This will give us the information we need to send to our contract after a user signs in.

Inside this file comment out these lines:
```
<p>mainnet: {userSession.loadUserData().profile.stxAddress.mainnet}</p>
<p>testnet: {userSession.loadUserData().profile.stxAddress.testnet}</p>
```

This doesn't affect functionality, we just don't want the addresses on the page.

Now, we are going to create a new component.

Inside the `component` directory create a file called `ContractCallGm.js`

```
import { useCallback, useEffect, useState } from "react";
import { useConnect } from "@stacks/connect-react";
import { StacksMocknet } from "@stacks/network";
import styles from "../styles/Home.module.css";

import {
  AnchorMode,
  standardPrincipalCV,
  callReadOnlyFunction,
  makeStandardSTXPostCondition,
  FungibleConditionCode
} from "@stacks/transactions";
import { userSession } from "./ConnectWallet";
import useInterval from "@use-it/interval";

const ContractCallGm = () => {
  const { doContractCall } = useConnect();
  const [ post, setPost ] = useState("");
  const [ hasPosted, setHasPosted ] = useState(false);

  function handleGm() {
    const postConditionAddress = userSession.loadUserData().profile.stxAddress.testnet;
    const postConditionCode = FungibleConditionCode.LessEqual;
    const postConditionAmount = 1 * 1000000;
    doContractCall({
      network: new StacksMocknet(),
      anchorMode: AnchorMode.Any,
      contractAddress: "ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM",
      contractName: "gm",
      functionName: "say-gm",
      functionArgs: [],
      postConditions: [
        makeStandardSTXPostCondition(
          postConditionAddress,
          postConditionCode,
          postConditionAmount
        )
      ],
      onFinish: (data) => {
        console.log("onFinish:", data);
        console.log("Explorer:", `localhost:8000/txid/${data.txId}?chain=testnet`)
      },
      onCancel: () => {
        console.log("onCancel:", "Transaction was canceled");
      },
    });
  }

  const getGm = useCallback(async () => {

    if (userSession.isUserSignedIn()) {
      const userAddress = userSession.loadUserData().profile.stxAddress.testnet
      const options = {
          contractAddress: "ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM",
          contractName: "gm",
          functionName: "get-gm",
          network: new StacksMocknet(),
          functionArgs: [standardPrincipalCV(userAddress)],
          senderAddress: userAddress
      };

      const result = await callReadOnlyFunction(options);
      console.log(result);
      if (result.value) {
        setHasPosted(true)
        setPost(result.value.data)
      }
    }
  });

  useEffect(() => {
    getGm();
  }, [userSession.isUserSignedIn()])

  useInterval(getGm, 10000);

  if (!userSession.isUserSignedIn()) {
    return null;
  }

  return (
    <div>
      {!hasPosted &&
        <div>
          <h1 className={styles.title}>Say gm to everyone on Stacks! 👋</h1>
          <button className="Vote" onClick={() => handleGm()}>
            gm
          </button>
        </div>  
      }
      {hasPosted && 
        <div>
          <h1>{userSession.loadUserData().profile.stxAddress.testnet} says {post}!</h1>
        </div>
      }
    </div>
  ); 
};

export default ContractCallGm;
```

There are a few things going on here so let's break it down.

The first function `handleGm` is where the bulk of the work is being done. 

```
  function handleGm() {
    const postConditionAddress = userSession.loadUserData().profile.stxAddress.testnet;
    const postConditionCode = FungibleConditionCode.LessEqual;
    const postConditionAmount = 1 * 1000000;
    doContractCall({
      network: new StacksMocknet(),
      anchorMode: AnchorMode.Any,
      contractAddress: "ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM",
      contractName: "gm",
      functionName: "say-gm",
      functionArgs: [],
      postConditions: [
        makeStandardSTXPostCondition(
          postConditionAddress,
          postConditionCode,
          postConditionAmount
        )
      ],
      onFinish: (data) => {
        console.log("onFinish:", data);
        console.log("Explorer:", `localhost:8000/txid/${data.txId}?chain=testnet`)
      },
      onCancel: () => {
        console.log("onCancel:", "Transaction was canceled");
      },
    });
  }
```

This function will execute on click of a button. 

The first portion of this function is making the actual contract call to our `say-gm` function via `doContractCall`. 

We pass to it the required options:

1. `network`: this is telling `doContractCall` what network to use to broadcast the function. There is mainnet, testnest, and devnet. We will be working with devnet and the network config for that is `new StacksMocknet()`.
2. `anchorMode`: this specifies whether the tx should be included in an anchor block or a microblock. In our case, it doesn't matter which.
3. `contractAddress`: this is the standard principal that deploys the contract (notice it is the same as the one provided in Devnet.toml).
4. `functionName`: this is the function you want to call
5. `functionArgs`: any parameters required for the function being called.
6. `postConditions`: Post conditions are a feature unique to Clarity which allow a developer to set conditions which must be met for a transaction to complete execution and materialize to the chain.

If you want to learn more about Post Conditions, check out [this](https://dev.to/krgrs/understanding-stacks-post-conditions-e65) great post.

We are using a standard STX post condition which is saying that the user will transfer less than or equal to 1 STX or the transaction will abort.

Upon successful broadcast, `onFinish` gets executed. We are simply logging some data.


Now let's take a look at the next function:

```
const getGm = useCallback(async () => {

    if (userSession.isUserSignedIn()) {
      const userAddress = userSession.loadUserData().profile.stxAddress.testnet
      const options = {
          contractAddress: "ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM",
          contractName: "gm",
          functionName: "get-gm",
          network: new StacksMocknet(),
          functionArgs: [standardPrincipalCV(userAddress)],
          senderAddress: userAddress
      };

      const result = await callReadOnlyFunction(options);
      console.log(result);
      if (result.value) {
        setHasPosted(true)
        setPost(result.value.data)
      }
    }
  });
```

This is making a call to our `read-only` function `get-gm`. You'll notice the formatting is slightly different, but the required options are the same ones we just discussed.

The main difference is that this is a callback function which we will call on an interval to check whether the user has called `say-gm`. Remember, `get-gm` will return `none` unless `say-gm` has successfully executed.

That's really all there is to it! The rest of the code is just React + JSX. Make sure you include this component in `index.js` and you're ready to move on to full interaction! 

Boot up DevNet and once it's running, you can start your frontend app with `npm start` and navigate to `localhost:3000`.

Click Connect Wallet to login.

This will open a pop-up with your web wallet accounts.


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/j44xcgx9vxoi2ewbgqfq.png)


> Notice that it shows Devnet in the top right corner and the account that you configured to DevNet earlier now has a balance of 100M STX. This is your test faucet from the `Devnet.toml` file.  

After logging in, your page should update and look like this:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7m7xm16u84ly37wyuufi.png)

Let's make a call to our `say-gm` function by clicking the purple button! Open your console in browser so you can see the logs.


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fuqnhwzgw8rssyb9bx9a.png)


You'll get the above tx request. It shows the `postCondition` we defined, the function being called, and fees.

Upon confirm, if everything went well you should see something like this in the browser console:


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wdo2hum4h8jmb3v61961.png)


The `onFinish` and `Explorer` logs are coming from the `handleGm` function. The `{type:}` is coming from the `getGm` callback. As you can see, it pinged every 10 seconds and it took just less than a minute for our tx call to broadcast and reflect.

Once that result comes in, the page should update to reflect that `UserPost` has updated on chain to map your address with the string "gm".

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tuj93vxv00dnokenn86o.png)


There it is! A front to back Stacks application from scratch.

One last fun feature to explore: copy the URL from the `Explorer` log and paste it in your browser. This will give you a visual of what the transaction call would look like on the [Stacks Explorer](https://explorer.stacks.co/?chain=mainnet).

I do hope this has been helpful in understanding the fundamentals of creating a full-stack project on Stacks from developing and testing the smart contracts to setting up a site that users can use to make on-chain calls and simulating what that might look like. Let me know what you think! 🚀

> Shameless plug: find me on Twitter [@themariaverse](https://twitter.com/themariaverse)