Building dApp with Near Protocol and React

Introduction

In this tutorial you can learn how to build web application using React.js, build and deploy a smart contract on the Near Blockchain and connect web app with the smart contract to have a working web application which will interact with the smart contract.

Prerequisites

To prepare the development environment, make sure you have installed nodejs 12+, yarn and the latest near-cli

You also need to create a testnet account, go to the testnet wallet and create one, it's easy and free:

Getting Started with the project

The easiest way to get started is using npx - Node Package Execute

Install npx as a global dependency

npm install -g npx

Generate starter project

Let's generate the new project. Go to a directory where you want to have your project in the terminal. In our case we will be using home directory.

For near dapps there is a npx binary create-near-app. It has some options to choose what type of frontend you are going to use and also what type of smart contract you are going to use. Here are the option you can use:

➜  ~ npx create-near-app -h
create-near-app <projectDir>

Create a new NEAR project

Options:
  --version   Show version number                                      [boolean]
  --frontend  template to use
            [choices: "vanilla", "react", "vue", "angular"] [default: "vanilla"]
  --contract  language for smart contract
                 [choices: "assemblyscript", "rust"] [default: "assemblyscript"]
  --help      Show help                                                [boolean]

Examples:
  create-near-app new-app  Create a project called "new-app"

For this tutorial we are going to use react as a frontend and assemblyscript as a smart contract.

Open terminal and execute the command:

npx create-near-app near-tutorial --frontend=react --contract=assemblyscript

Wait a bit to download everything and when it finish you will see something like this:
New project generated

In general our new project is ready to be started, the only thing you still need is to login in your near testnet account you should have create before. To do this open the terminal and call:

near login

It should open the browser where you approve login, after that you are ready to interact with the near blockchain using `near cli.

That's it we have created our project, now we can get hands dirty in the code. Open the project in you favourite IDE, the recommended option is using free VS Code:

bash
cd near-tutorial
code .

Project structure

Our newly created project has several main places:

  • src - React source code
  • contract - Smart contract source code
  • package.json - Project dependencies and running scripts
  • Readme.md - Project documentation and development tips
  • neardev - Configuration for smart contract development

Running the project

First of all we need to install dependencies using yarn command:

bash
yarn

It can take some minutes depending on your network, be patient :)

After that we can already run the project in the dev environment. You can use one simple command:

bash
yarn dev

After a couple of seconds you should see something similar in your terminal and it should also open the app in your default browser:
Yarn dev result

The app url http://localhost:1234/ opened in browser should look like this:
Browser View

In the dev console you can also see that you dev smart contract was deployed to the blockchain, it starts with dev- and have some random numbers, in our case its dev-1638480997606-36755974489881. You can also see the link to the smart contract transaction deployment: https://explorer.testnet.near.org/transactions/7N4BWLVyuo9gXp9sGe8WKXBZEX7iJUq5AvZXAGqoRij1
Opening the link in your terminal will show you similar:
Smart Contract Transaction

Now let's jump in the browser and test how it works.
Generated project has predefined greeting smart contract, you can enter the custom greeting message and it will save it in the smart contract storage, change it to something custom and press save. It should redirect you to the wallet where you can sign the smart contract with your near testnet account.

Press allow to approve transaction:
Approve transaction

After successful approval you will be redirected back to the ui and will see the new greeting which is loaded from the smart contract:

It works, let's see how its done

Smart contract implementation and cli interaction:

Smart contract is located in contract/assembly/index.ts:

It has the default message which we saw in the browser right after the opening:

javascript
const DEFAULT_MESSAGE = "Hello";

And it has two methods getGreeting(accountId: string) and setGreeting(message: string)

Mutating method setGreeting

javascript
export function setGreeting(message: string): void {
const accountId = Context.sender;
// Use logging.log to record logs permanently to the blockchain!
logging.log(
Saving greeting "${message}" for account "${accountId}");
storage.set(accountId, message);
}

As you can see this method contains one argument message which was send when we approved the transaction. Inside the method we are extracting a the sender accountId from the Context class:

javascript
const accountId = Context.sender;

Context is a class provided from the near-sdk-as and it has some useful data you may need in your during the development:

You may find the whole class clicking on it in IDE or you can also check it out on near-sdk-as docs

After extracting the accountId we are using another class storage and its method storage.set:

javascript
storage.set(accountId, message);

Storage is a key-value store that is persisted on the NEAR blockchain. Read the docs to check all the available methods.

Lets test the method using the near cli.

To make is easy we will set the CONTRACT_NAME env variable, and to do so we can call neardev/dev-account.env which has our contract name inside:

Call this in the terminal and check if you have exported the variable:

bash
source neardev/dev-account.env
echo $CONTRACT_NAME

Call result:
CONTRACT_NAME

One more thing to do is to set our testnet account as ID env variable:

bash
export ID=your-account.testnet
echo $ID

Call result:
Export $ID

If you want to pass a method argument using near-cli you can pass a json string afther the contract name.
Now we can set the greeting using near-cli:

bash
near call $CONTRACT_NAME setGreeting '{"message": "Near CLI Greeting"}' --accountId $ID

It will call the smart contract and print you the transaction id:
setGreeting from CLI

Readonly method getGreeting

getGreeting method is a readonly method, which mean we cannot use the context.sender to get the account id, its only accessible in mutating state calls:

javascript
export function getGreeting(accountId: string): string | null {
// This uses raw
storage.get, a low-level way to interact with on-chain
// storage for simple contracts.
// If you have something more complex, check out persistent collections:
// https://docs.near.org/docs/concepts/data-storage#assemblyscript-collection-types
return storage.get < string > (accountId, DEFAULT_MESSAGE);
}

It is doing one call to storage to get the greeting from the smart contract storage or the default method, if there is no message in the storage for the account we use. Readonly methods are using view instead of call we used for setGreeting:

bash
near view $CONTRACT_NAME getGreeting "{\"accountId\": \"$ID\"}"

Boom, we can see the greeting we set in the previous step:

Let go to the browser and refresh the page to verify that our message is also there. If everything goes well you will see this after refresh:
Check browset

How React connects with Near

Now lets check how we interact with the Near Blockchain in frontend

In our react application we have two configuration files where we connect to the blockchain: config.js and utils.js:

Inside config.js we define our contract name, which is also taken from environment variable :

javascript
const CONTRACT_NAME = process.env.CONTRACT_NAME || "near-tutorial";

And we also have getConfig function with the blockchain configuration for testnet, mainnet and some other environments:

`javascript
function getConfig(env) {
switch (env) {

case 'production':
case 'mainnet':
return {
networkId: 'mainnet',
nodeUrl: 'https://rpc.mainnet.near.org',
contractName: CONTRACT_NAME,
walletUrl: 'https://wallet.near.org',
helperUrl: 'https://helper.mainnet.near.org',
explorerUrl: 'https://explorer.mainnet.near.org',
}
case 'development':
case 'testnet':
return {
networkId: 'testnet',
nodeUrl: 'https://rpc.testnet.near.org',
contractName: CONTRACT_NAME,
walletUrl: 'https://wallet.testnet.near.org',
helperUrl: 'https://helper.testnet.near.org',
explorerUrl: 'https://explorer.testnet.near.org',
}
...
}
`

The next file is utils.js where we use the config from config.js, wand the main is initContract() method, where we connect to the blockchain rpc and list all the available methods in our contract:

`javascript
import { connect, Contract, keyStores, WalletConnection } from "near-api-js";
import getConfig from "./config";

const nearConfig = getConfig(process.env.NODE_ENV || "development");

// Initialize contract & set global variables
export async function initContract() {
// Initialize connection to the NEAR testnet
const near = await connect(
Object.assign(
{ deps: { keyStore: new keyStores.BrowserLocalStorageKeyStore() } },
nearConfig
)
);

// Initializing Wallet based Account. It can work with NEAR testnet wallet that
// is hosted at https://wallet.testnet.near.org
window.walletConnection = new WalletConnection(near);

// Getting the Account ID. If still unauthorized, it's just empty string
window.accountId = window.walletConnection.getAccountId();

// Initializing our contract APIs by contract name and configuration
window.contract = await new Contract(
window.walletConnection.account(),
nearConfig.contractName,
{
// View methods are read only. They don't modify the state, but usually return some value.
viewMethods: ["getGreeting"],
// Change methods can modify the state. But you don't receive the returned value when called.
changeMethods: ["setGreeting"],
}
);
}
`

We expand the global window object with the methods we will be using to interact with the blockchain and our smart contract. And here we also list viewMethods which we were calling with near view and changeMethods which we were calling with near call. So whenever you add new methods to your contract you have to update this file and list all the methods in the appropriate section, so that you can also use them later in your React Components.

In src/App.js you can see how the contract is used:

`javascript
// The useEffect hook can be used to fire side-effects during render
// Learn more: https://reactjs.org/docs/hooks-intro.html
React.useEffect(
() => {
// in this case, we only care to query the contract when signed in
if (window.walletConnection.isSignedIn()) {
// window.contract is set by initContract in index.js
window.contract
.getGreeting({ accountId: window.accountId })
.then((greetingFromContract) => {
setGreeting(greetingFromContract);
});
}
},

// The second argument to useEffect tells React when to re-run the effect
// Use an empty array to specify "only run on first render"
// This works because signing into NEAR Wallet reloads the page
[]
);
`

Making Changes

So now when we know how everything is connected to each other let's make it ours by making some changes.

Updating Smart Contract

Let's expand our smart contract with some properties, like date when the most recent greeting has been set.

In VSCode open contract/assemble/index.ts and add replace setGreeting method with the following:

javascript
export function setGreeting(message: string): void {
const accountId = Context.sender;
const timestamp = Context.blockTimestamp;
// Use logging.log to record logs permanently to the blockchain!
logging.log(
Saving greeting "${message}" with timestamp: ${timestamp} for account "${accountId}"
);
storage.set(accountId, message);
storage.set(
${accountId}_last_updated,
${new Date(timestamp / 1000000).toDateString()} ${new Date(
timestamp / 1000000
).toTimeString()}
);
}

We have added two lines, first one is getting the block timestamp, which is provided in nanoseconds:

javascript
const timestamp = Context.blockTimestamp;

Second one - convert set the storage to contains last update date of the greeting:

javascript
storage.set(
${accountId}_last_updated,
${new Date(timestamp / 1000000).toDateString()} ${new Date(
timestamp / 1000000
).toTimeString()}
);

Then let's add the method to get last update value from the smart contract using the ${accountId}_last_updated key:

javascript
export function getUpdateDate(accountId: string): string | null {
return (
storage.get < string > (
${accountId}_last_updated, "No custom greeting.")
);
}

Updating React

Now let's use our new method in the React Code.

First of all we need to add them to the contract definition inside src/utils.js. Go and add new method getUpdateDate to viewMethods and save file so it will look like this:

Then open src/App.js and add a new state variable to store our update date:

javascript
const [updateDate, setUpdateDate] = React.useState();

After that inside useEffect hook where we are getting the greeting add one more call to get the getLastUpdate and when we fetch the value we can update our updateDate state hook by calling setUpdateDate. The code we add should look as following:

javascript
window.contract
.getUpdateDate({ accountId: window.accountId })
.then((greetingUpdateDate) => {
setUpdateDate(greetingUpdateDate);
});

And here how the file should look after we added those changes:

And the last part is to show the updateDate in the UI. Find the h1 tag where you show current greeting and add some other tag for example h3 after to show the last update date.

`javascript

Last Update: {updateDate}

`

Then if you open the browser you will see the default response because we have to call setGreeting again to save the timestamp in the smart contract storage.
So let's update the greeting and press save again, approve the transaction and when getting back we will see the date (refresh the page to see the latest changes):

Great we did it, it looks awesome, isn't it?

When you save any file in your project it is automatically rebuild and redeployed to the dev in terminal, so you should be ready to use it. If it didn't happen or you have stopped your app, just use yarn dev again and it will start up.

Deploying to the GitHub Pages

The project is already set to be deployed to the Github Pages, check package.json for all the commands available, but to simply deploy it as is you can use yarn deploy:pages or to deploy everything including your smart contract you can use command yarn deploy which will build and deploy both the contract, and also the ui app.

But make sure to first commit and push your app to Github and also add the homepage property to the package.json. More details can be found here

Conclusion

That's it for now, we learned how to generate a new react app connect it with the near smart contract, how to add new methods to the smart contract, and how to use them in the UI.

You can add some more methods by your own, for example add some change methods to have some custom logic for your greeting, for example return it as a reversed string, or maybe store some custom colours or font settings for the greeting in the smart contract.

The tutorial source code is accessible here and demo app is deployed to GithubPages.

Happy coding!

21