Customizing the front-end

Now that you have a basic understanding of how to create, build, and deploy a simple program and are familiar with the default project files and sample front-end, you might want to start experimenting with different ways to customize the front-end user experience for your project.

This tutorial illustrates using a simple React framework to create a new front-end for the default sample program and guides you through some basic modifications to customize the interface displayed. Later tutorials expand on the techniques introduced here, but if you already know how to use CSS, HTML, JavaScript, and React or other frameworks to build your user interface, you can skip this tutorial.

Currently, you can only use Javascript to implement the front-end for your canister. This tutorial illustrates using the React framework to manage the Document Object Model (DOM) for your canister. Because React has its own custom DOM syntax, you need to modify the webpack configuration to compile the front-end code, which is written in JSX. For more information about learning to use React and JSX, see Getting started on the React website.

Create a new project

To create a new project directory for your custom front-end application:

  1. Open a terminal shell on your local computer, if you don’t already have one open.

  2. Change to the folder you are using for your Internet Computer sample projects.

  3. Check that you have node.js installed locally by running the following commands:

    which node
    which npm

    If you don’t have node.js installed, you should download and install it before continuing to the next step. For information about installing node for your local operating system and package manager, see the Node website.

  4. Create a new project by running the following command:

    dfx new custom_greeting

    The dfx new custom_greeting command creates a new custom_greeting project.

  5. Change to your project directory by running the following command:

    cd custom_greeting

Install the React framework

If you’ve never used React before, you might want to explore the Intro to React tutorial or the React website before editing the front-end code.

To install required framework modules:

  1. Install the React module by running the following commands:

    npm install --save react react-dom
    npm install --save typescript ts-loader
  2. Open the dfx.json configuration file in a text editor to review the default frontend, main, and serve_root settings.

    Note that the frontend.entrypoint setting specifies the path to a file—in this case, the index.js file—to use as your application entry point. If you had a different starting point—for example, a custom index.html file—you would modify this setting.

    For this tutorial, change the entry file name to index.jsx to enable adding HTML directly inside Javascript as a template.

    You should also note that the serve_root setting specifies the path to a directory for static assets that will be included in your canister when you build your project. If you had custom cascading stylesheet (CSS) or JavaScript files, you would include them in the project’s public source folder but they would be served from this assets directory.

  3. Close the dfx.json file to continue.

  4. Change to the src/custom_greeting directory.

    cd src/custom_greeting
  5. Open the main.mo file to review the default code, if needed.

    For this tutorial, you are going to use the default main.mo file. Later, you can return to this file and make changes to see how you can use the front-end to reflect changes in your program code.

  6. Close the main.mo file to continue.

Modify the front-end files

You are now ready to create a new front-end for the default program.

  1. Navigate back to the root of your project directory.

  2. Open the webpack configuration file (webpack.config.js) in a text editor to add the following module key above the plugins section:

    module: {
      rules: [
        { test: /\.(js|ts)x?$/, loader: "ts-loader" }
      ]
    },

    This setting enables the program to use the ts-loader compiler for the index.jsx file.

  3. Create a new file named tsconfig.json, open the file in a text editor, then copy and paste the following into the file:

    {
        "compilerOptions": {
          "target": "es2018",        /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */
          "module": "commonjs",      /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */
          "lib": ["ES2018", "DOM"],  /* Specify library files to be included in the compilation. */
          "allowJs": true,           /* Allow javascript files to be compiled. */
          "jsx": "react",            /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
        }
    }
  4. Save your changes and close the tsconfig.json file to continue.

  5. Change to the custom_greeting/src/custom_greeting/public directory.

  6. Open the default index.js file in a text editor and delete the existing content.

  7. Copy and paste the following sample code into the index.js file:

    import custom_greeting from 'ic:canisters/custom_greeting';
    import * as React from 'react';
    import { render } from 'react-dom';
    
    class MyHello extends React.Component {
      constructor(props) {
        super(props);
        this.state = {
          name: 'Name',
          message: '',
        };
      }
    
      async doGreet() {
        const greeting = await custom_greeting.greet(this.state.name);
        this.setState({ ...this.state, message: greeting });
      }
    
      onNameChange(ev) {
        this.setState({ ...this.state, name: ev.target.value });
      }
    
      render() {
        return (
          <div style={{ "font-size": "30px" }}>
            <div style={{ "background-color": "yellow" }}>
              <p>Greetings, from DFINITY!</p>
              <p> Type your message in the Name input field, then click <b> Get Greeting</b> to display the result.</p>
            </div>
            <div style={{ "margin": "30px" }}>
              <input id="name" value={this.state.name} onChange={ev => this.onNameChange(ev)}></input>
              <button onClick={() => this.doGreet()}>Get Greeting!</button>
            </div>
            <div>Greeting is: "<span style={{ "color": "blue" }}>{this.state.message}</span>"</div>
          </div>
        );
      }
    }
    
    render(<MyHello />, document.getElementById('app'));
  8. Rename the modified index.js file as index.jsx by running the following command:

    mv index.js index.jsx

Build the program

You are now ready to compile the program with the new front-end as part of the WebAssembly module.

To build the program with the new front-end:

  1. Open a terminal shell on your local computer, if you don’t already have one open.

  2. Navigate to the root of your custom_greeting project folder.

    Note that this step is required because you must run the dfx build command from within the project directory structure.

  3. Build the executable canister by running the following command:

    dfx build

    The command displays output indicating that the build is successful.

      Done building canisters...
      Done building frontend...
      Bundling frontend assets in the canister...

Start the local network and deploy

You now have a program that can be deployed on your local replica network.

To deploy the program on your local network:

  1. Open a terminal shell on your local computer, if you don’t already have one open, and navigate to your custom_greeting project directory.

  2. Start the Internet Computer network on your local computer by running the following command:

    dfx start --background
  3. Deploy your custom_greeting project on the local network by running the following command:

    dfx canister install custom_greeting

    The command output displays output similar to the following:

    Installing code for canister custom_greeting, with canister_id ic:19F69036D7308B0DEE
    Feb 04 00:13:28.366 INFO Successfully inserted an ingress
    message into IngressPool, Application: P2P/ArtifactPool
    Feb 04 00:13:28.717 INFO Created checkpoint @335
    in 4.989993ms, StateManager: 1

Viewing the front-end

  1. Open a browser and navigate to the address and port number specified in the dfx.json configuration file.

    By default, the URL uses the localhost address (127.0.0.1) and port number 8000, so you can navigate to the front-end for the hello_world program using 127.0.0.1:8000 for the URL.

    In addition to the host and port, the URL you specify needs to include canisterId parameter to identify the canister that you want the web server to display. To specify the canister, append the identifier using the following syntax:

    ?canisterId=ic:canister_identifier

    For example, the full URL should look similar to the following but with the ic:_canister_identifier_ that was returned by the dfx canister install command:

    http://localhost:8000/?canisterId=ic:81DDA04F69F40FEEAC
  2. Verify that you are prompted to type a greeting.

    For example:

    Sample front-end

  3. Replace Name in the input field with the text you want to display, then click Get Greeting to see the result.

    For example:

    Greeting result

Revise the front-end and test your changes

After viewing the front-end, you might want to make some changes.

To modify the front-end:

  1. Open the index.jsx file in a text editor and modify its style settings. For example, you might want to change the font family and use a placeholder for the input field by making changes similar to the following:

    import custom_greeting from 'ic:canisters/custom_greeting';
    import * as React from 'react';
    import { render } from 'react-dom';
    
    class MyHello extends React.Component {
      constructor(props) {
        super(props);
        this.state = {
          name: '',
          message: '',
        };
      }
    
      async doGreet() {
        const greeting = await custom_greeting.greet(this.state.name);
        this.setState({ ...this.state, message: greeting });
      }
    
      onNameChange(ev) {
        this.setState({ ...this.state, name: ev.target.value });
      }
    
      render() {
        return (
          <div style={{ "font-family": "sans-serif" }}>
          <div style={{ "font-size": "30px" }}>
              <p>Greetings, from DFINITY!</p>
              <p> Type your message in the Name input field, then click <b> Get Greeting</b> to display the result.</p>
    
            <div style={{ "margin": "30px" }}>
              <input id="name" placeholder="Type text here" value={this.state.name} onChange={ev => this.onNameChange(ev)}></input>
              <button onClick={() => this.doGreet()}>Get Greeting!</button>
            </div>
            <div>Greeting is: "<span style={{ "color": "green" }}>{this.state.message}</span>"</div>
          </div>
        </div>
        );
      }
    }
    
    render(<MyHello />, document.getElementById('app'));
  2. Rebuild the project with your changes by running the following command.

    dfx build
  3. Deploy your project changes and note your new canister identifier.

    dfx canister install custom_greeting
  4. View the results in the browser by changing the URL to use the new canister identifier.

    For example:

    Modified styles on your entry page

  5. Type a new message and see your new greeting. For example:

    Modified greeting result

    In the background, the Internet Computer process logs the message request and creates a new checkpoint.

    Feb 04 19:57:29.100 INFO Successfully inserted an ingress message into IngressPool, Application: P2P/ArtifactPool
    Feb 04 19:57:29.273 INFO Created checkpoint @246690 in 25.547581ms, StateManager: 1
  6. Stop the Internet Computer processes running on your local computer by running the following command:

    dfx stop