Lesson 2: Using React for on-chain frontends

Adding and configuring the React library

Carsten
Jacobsen

This lesson is a continuation of the first lesson, where the default Hello World dapp was installed and deployed locally. The frontend of the Hello World dapp is written in HTML/CSS and Javascript, and even though it works great for a small simple dapp, it's not ideal for larger dapps.

React is still one of the most popular frontend libraries, and it happens to be the one I have most experience with, so that's why it's chosen for this project. The objective of this lesson is to add the React library and add the necessary configuration. As a bonus a router will be added, so a simple navigation and subpages can be implemented. In lesson 3 we will rebuild the Hello World dapp using React, but first we want to get React fully functional on-chain.

The dapp code

This lesson contains two parts, a code part and a configuration part. The configuration part is where we make this work running on-chain on the Internet Computer, but let’s first create the dapp code.

Install dependencies

First step is to install the dependencies, which are the main React library and supporting libraries, including a router. From the root of the project (where the package.json file is) run this command:

% npm install react react-dom react-router-dom @types/react --save
That’s all needed for now.

Creating the App.tsx file

This file is where the routes are defined. In this lesson there’s only two routes, one for a Home page and one for an About page. For now we just keep the code for the two pages in the App.tsx file. Eventually we want to move the code to separate, since they may contain a lot of code and clutter the App.tsx file. As the project grows, having the page code separate also helps keep the code structured and easy to navigate.

Routes
The route is very simple at this point:

import * as React from "react";
import { RouteObject, Link, useRoutes } from "react-router-dom";

export default function App() {
   let routes: RouteObject[] = [
      {
         path: "/",
         element: <Home />,
      },
      {
         path: "/about",
         element: <About />,
      },
   ];

   let element = useRoutes(routes);

   return (
      <>
         {element}
      </>
   );
}
First the necessary modules are imported. Then we define the dapp’s main function, App.

The RouteObject routes defines the routes, and which page to load for each route. The Home page is loaded by default, and the About page is defined as a child of the Home page. A Javascript object equivalent to theelement is returned with useRoutes(), and the App can be inserted as antag.

Pages
The page code is very simple for now, we will add to it later, for getting things up and running, it’s good to keep it simple.

function Home() {
   return (
      <div>
         <h2>Home</h2>
         <p>
            <Link to="/">Go to the about page</Link>
         </p>
      </div>
   );
}

function About() {
   return (
      <div>
         <h2>About</h2>
         <p>
            <Link to="/">Go to the home page</Link>
         </p>
      </div>
   );
}
That’s all the code we need for the App.tsx file.

Creating the index.js file

The index.js file is the entrypoint of the React frontend, and for this lesson it’s not doing a lot - the main purpose is to render the React application we just created. Replace the content of the current index.js file from the Hello World dapp with this code:

import * as React from "react";
import * as ReactDOM from "react-dom/client";
import { BrowserRouter } from "react-router-dom";
import App from "./App";

ReactDOM.createRoot(document.getElementById("root")).render(
   <React.StrictMode>
      <BrowserRouter>     
         <App />
      </BrowserRouter>
   </React.StrictMode>
);
We want to render App inside a DOM element in the HTML code, and we can do so by calling createRoot() on a DOM element, which will create a React root for rendering. The root will render App by calling render().

DOM element in the index.html file

The last step of creating the code is to add the DOM element that will be used for rendering App. In this lesson we are not going to rebuild the Hello World dapp, just getting React installed and configured, so the HTML file is very simple. Replace the current code in the index.html with this:

<!DOCTYPE html>
<html lang="en">
   <head>
      <meta charset="UTF-8" />
      <meta name="viewport" content="width=device-width" />
   </head>
   <body>
      <main>
         <div id="root" />
      </main>
   </body>
</html>

Configuration

Now the code is done, there are a few things we need to set up and install to make the React application work in the canister.

Adding loaders

Webpack is used to bundle the code and assets, and with the default configuration used for the Hello World dapp, there’s no support for the React (TypeScript) files. Let’s first install the needed loaders by running this command:

% npm install ts-loader style-loader css-loader --save
When the loaders are installed, we can add them to the webpack configuration (webpack.config.js) in the project root:

module.exports = {
   ...
   module: {
      rules: [
         { test: /\.(js|ts|tsx|jsx)$/, loader: "ts-loader" },
         { test: /\.css$/, use: ['style-loader','css-loader'] }
      ]
   },
   ...
}

Create tsconfig.json file

The final step is to create a TypeScript configuration file in the project root:

{
   "compilerOptions": {
      "outDir": "./dist/",
      "noImplicitAny": true,
      "module": "es6",
      "target": "es5",
      "jsx": "react",
      "allowJs": true,
      "moduleResolution": "node"
   }
}
That's it!

Test the dapp

If you just completed Lesson 1, you should still have dfx running. If not, open a terminal and run this command from the project root (where the dfx.json file is):

% dfx start --clean
With dfx running we can deploy the dapp and run it locally:

% dfx deploy

...
Deployed canisters.
URLs:
   Frontend canister via browser
      bd_frontend: http://127.0.0.1:4943/?canisterId=bd3sg-teaaa-aaaaa-qaaba-cai
   Backend canister via Candid interface:
      bd_backend: http://127.0.0.1:4943/?canisterId=be2us-64aaa-aaaaa-qaabq-cai&id=bkyz2-fmaaa-aaaaa-qaaaq-cai
Copy the frontend canister URL and open it in a browser. We will now see the React application loaded in the browser.

For the About link to work, we need to format the URL a bit differently, otherwise we will lose the canisterId parameter. Use this format instead, and the links to the About and Home pages will work:

http://bd3sg-teaaa-aaaaa-qaaba-cai.localhost:4943

Summary

We now have a fully functional React-based dapp running 100% on-chain (locally). This lesson showed how to modify the Hello World dapp to be a basic React application, and how to configure the dapp to work with React. At the time of writing this lesson there’s no clear documentation or tutorials that shows the configuration part, but as shown here, the configuration is not really specific to building on the Internet Computer, it’s more a general configuration you need to do when using webpack.

Useful links:

Lesson 1: Getting to "Hello World"

Get the default sample dapp up and running

Carsten
Jacobsen

When we as developers are new to a technology, the good old Hello World app (or ToDo app) is a great way to get started. If you are already familiar with the Internet Computer blockchain (ICP), and have deployed your first dapp, then this post is probably going to be irrelevant.

Hey, you misspelled app, you wrote dapp

Nope, dapp is short for decentralized application, and is a common term for apps running on blockchain. Dapp is a broad term, just like app, it can be a mobile application, a web-based application, a command line application etc. Some dapps are even a combination of Web2 and Web3 - meaning some parts of the application runs on Web2 (e.g. AWS).

I'll save that discussion for later, but I believe for an application to be truly decentralized, it must run 100% on-chain. As long as part of a dapp is hosted on a Web2 infrastructure, it's still subject to censorship, and it's still under control of the owner of the hosting account. The Internet Computer makes it possible to run the dapp 100% on blockchain, and if governed by a DAO (Decentralized Autonomous Organization), the dapp is truly decentralized. But more about that in another post, let's get the Hello World up and running.

Prerequisites

You computer must have a few tools and libraries installed before we can proceed. You will need to have Node.js installed, and the lastest version will be ideal. You will also need to have the Internet Computer CLI tool (dfx) installed, it's used for building and deploying your smart contracts to either localhost or the mainnet. To install dfx, run this command in your terminal:

% sh -ci "$(curl -fsSL https://internetcomputer.org/install.sh)"
When dfx is installed, run this command to start a local replica, which serves as a single node testnet:

% dfx start

Running dfx start for version 0.15.2
Initialized replica.
Dashboard: http://localhost:35375/_/dashboard
The local testnet is now up and running, and we can create a new project - the Hello World project. The replica will keep running in the terminal, so for the next steps you will need to open a new terminal. The dfx start command can also run in the background if the you add --background to the start command.

Create a new project

When you use dfx to create a new project, a default Hello World-project with both a frontend, a backend and all configuration files are created. The default project can is a simple dapp that has an input (name) field on the website that takes a string, it sends it to the backend, adds a hello-text and returns it to the frontend.

Before creating the new project, you need to decide which programming language you want to use for the backend smart contract. The official languages of the Internet Computer are Motoko and Rust, but thanks to community contributions, TypeScript and Python can also be used. I have decided to use Rust for this website, primarily because I already use Rust in another project. The steps are more or less the same regardless of language.

Let's get this project going! Run this command to create the default Hello World-project in Rust (see the documentation to use another language):

% dfx new bd --type=rust
- where bd is the project name (short for Blockchains & Dreams). If you are choosing Rust as a backend language, you may need to add the wasm32-unknown-unknown target to your Rust toolchain:

% rustup target add wasm32-unknown-unknown

Deploy the dapp locally

You should now be able to deploy the dapp locally by running the dfx deploy command. You must be in the project root when you run the dfx command, if you see the dfx.json file you are in the right directory.

% cd db
% dfx deploy

Deployed canisters.
URLs:
  Frontend canister via browser
    bd_frontend: http://127.0.0.1:4943/?canisterId=canisterId
  Backend canister via Candid interface:
    bd_backend: http://127.0.0.1:4943/?canisterId=canisterId&id=canisterId
Two canister smart contracts have now been deployed to the local replica. The smart contract canister Ids can be looked up in this JSON file: ./.dfx/local/canister_ids.json. One for the frontend and one for the backend. You can test the dapp by following the link to the frontend canister in a browser, and should see this:
The bd_backend url links to the Candid UI, which is a tool for making calls to the backend in a very easy way, using an UI. This dapp has a backend function called greet() which is called from the UI with a string (name), and it returns a new string. In the screenshot above the name "World" is entered in the form and sent to the backend, and the backend returns "Hello, World!". In the Candid UI the backend function can be called directly:
The backend function can also be called from the terminal, using dfx. The call looks like this:

% dfx canister call bd_backend greet World

("Hello, World!")
The call is made by specifying the canister name (bd_backend), the function name and the value you want to pass to the function. If you are not sure what the canister name is, you can lookup the name in the dfx.json file in the project root.

A closer look at the project

So far we have created a new project, deployed it and tested it using the frontend, the Candid UI and from the terminal. Now we know what the dapp can do, let's dive into the code. The file structure looks like this:

bd/
├── src
│   ├── bd_backend
│   │   ├── src
│   │   │   └── lib.rs
│   │   ├── bd_backend.did
│   │   └── Cargo.toml
│   │
│   └── bd_frontend
│       ├── assets
│       │   ├── logo2.svg
│       │   └── main.css	
│       └── src
│           ├── index.html
│           └── index.js	
└── dfx.json
dfx.json
In the root of the project directory you will find the dfx.json, which is the project's main configuration file. The file is defining the canisters, canister types, dependencies, code entry points etc. For more information about the configuration file, see the documentation.

bd_backend
This directory contains backend files, and if you chose another programming language than Rust (used in this post), then it might look different, but high level it will be the same. There's going to be a code file with the greet() function and the Candid file, which is describing the backend interface. In my case I also have a Cargo.toml file with the Rust dependencies, but if you created the project with e.g. the standard Motoko language, you will not have this file.

The src directory contains the main backend code file, in this case the lib.rs file. The file only contains one function for the Hello World, the greet() function:

#[ic_cdk::query]
fn greet(name: String) -> String {
  format!("Hello, {}!", name)
}
bd_backend.did
The Candid file describes the backend interface, and is automatically generated for languages like Motoko, but for Rust this file must be updated manually when adding a new public function. The Candid interface is to dapps on the Internet Computer what REST APIs are to Web2 apps, and then some...

service : {
  "greet": (text) -> (text) query;
}
bd_frontend
The frontend smart contrast canister has two directories. An assets directory for images and static files (like CSS files), and a src directory for source code files. Assets files will be served from the root, so images with the path /assets/my_pic.png will be served as /my_pic.png when deployed.

The src directory contains two files, the index.html and index.js files. The HTML file contains the form that will submit the name input to the backend, and the section that will display the response.

<form action="#">
  <label for="name">Enter your name:  </label>
  <input id="name" alt="Name" type="text" />
  <button type="submit">Click Me!</button>
</form>
<section id="greeting"></section>
The index.js file contains the Javascript code to handle form submit, displaying the response from the backend and the integration with the backend canister.

import { bd_backend } from "../../declarations/bd_backend";

document.querySelector("form").addEventListener("submit", async (e) => {
  e.preventDefault();
  const button = e.target.querySelector("button");
  const name = document.getElementById("name").value.toString();
  button.setAttribute("disabled", true);

  const greeting = await bd_backend.greet(name);

  button.removeAttribute("disabled");
  document.getElementById("greeting").innerText = greeting;

  return false;
});
First the backend Candid declaration is imported. This will make the public backend functions accessible to the frontend. That's how easy it is to integrate backend smart contracts with frontend smart contracts!

Most of the functionality is straight forward Javascript. An eventlistener, triggered by the Submit button, gets the value of the name input field and calls the greet() backend function. The function returns a string, which is rendered in the greeting section tag.

Summary

The default starter project, that is provided when you create a new project with the dfx new command, may be simple, but it has everything you need to learn about the anatomy of a dapp, and get started building your own dapp (simply add on to the default dapp). This post illustrated the different parts of a dapp, built on the Internet Computer blockchain. For deeper information, see the documentation, there are links below.

Useful links:

Welcome! Let's dive in...

Quick intro to this page and what's to come

Carsten
Jacobsen

Hello, my name is Carsten, thanks for stopping by! Since this is the first post on this page,
I would like to start off with a quick intro.


About myself
I have worked in tech for more than 20 years, and have primarily worked with software development, both desktop and web-based. The past 6 years I've worked as a Developer Evangelist and Developer Relations Engineer, where I've worked with developers to get excited about new tech, and get started building with new tech.

The past 2.5 years, until I recently started my own business, I worked for the DFINITY Foundation, the organisation behind the Internet Computer blockchain. This was my first job in blockchain, and the first time I really had experience with developing on blockchain, and I got hooked!

Writing about blockchain
This website will be my outlet for thoughts and experiences related to blockchain, but also other tech topics like open source and entrepreneurship. The content will be biased towards the Internet Computer blockchain, since that's what I believe is the best chain for building applications, it's the chain I have most experience with and it's the chain I have decided to build my own business on.

This website is 100% on-chain!
This website is actually deployed 100% on-chain, on the Internet Computer. I do not use AWS, Google Cloud or any other type of hosting, both frontend and backend is running on-chain in smart contract canisters. As I'm writing this post, the website is static, without a backend, but follow along as I keep developing the website.

Follow along as i build this website
The idea is to build out the website to be a fully functional on-chain blog, with everything you know from Web2 blogs (post, pages, comment, share, like etc.). I'll do a post about every upgrade, with code samples and explanations, and probably make the different versions availble. The production repo of the live website will be available on GitHub as open source.

Cheers!