App.js

Note

There is no code installation or command execution required in this section.

The Web user interface code is in the App.js file in the client/src directory. We define App as a funtional component of React.

simple-storage-app
|--client
|  |--src
|  |  |--App.js

Let’s see what this App.js file is all about. In any React project, App.js acts as the main component in the React application. The basic structure of any React component is like this:

function App() {
    return (
    <div className="App">

        </div>
    )
}

export default App

The complete App.js file is shown below.

import React, { useEffect, useState } from 'react';
import 'antd/dist/antd.css';
import { Card, Form, InputNumber, Button, Layout, Row, Col, Divider } from 'antd';
import Web3 from 'web3';
import SimpleStorageArtifact from './contracts/SimpleStorage.json'

function App() {
  const [ currentValue, setCurrentValue ] = useState('');

  const web3 = new Web3(Web3.givenProvider || 'http://127.0.0.1:7545');
  const simpleStorageAbi = SimpleStorageArtifact.abi;
  const simpleStorageAddress = SimpleStorageArtifact.networks[5777].address;
  const SimpleStorageContract = new web3.eth.Contract(simpleStorageAbi, simpleStorageAddress);

  const getCurrentValue = async () => {
    // Get the value from the contract.
    const response = await SimpleStorageContract.methods.get().call();

    // Update current value from the smart contract storage value.
    setCurrentValue(response);
  }

  useEffect(() => {
    getCurrentValue();
  })

  const submitButtonHandler = async (values) => {
    const accounts = await window.ethereum.enable();
    // Stores input value in smart contract.
    await SimpleStorageContract.methods.set(values.storageValue).send({ from: accounts[0] });

    // Get the value from the contract to prove it worked.
    getCurrentValue();
  };

  return (
    <Layout style={{ minHeight: '100vh', padding: '16px' }} >
      <Row>
        <Col xs={24} lg={15} xl={12} xxl={9}>
          <Card title="Simple Storage" style={{ margin: '0px' }}>
            <Form
              labelCol={{
                span: 5,
              }}
              wrapperCol={{
                span: 16,
              }}
              layout="horizontal"
              initialValues={{
                size: componentSize,
              }}
              onValuesChange={onFormLayoutChange}
              size={componentSize}
              onFinish={submitButtonHandler}
              labelAlign='left'
            >
              <Form.Item label="Current value">
                <span>{currentValue}</span>
              </Form.Item>
              <Divider></Divider>

              <Form.Item label="Value" name="storageValue" rules={[{ required: true, message: 'Please enter value!' }]} >
                <InputNumber
                  min="0"
                  max="999999999999999"
                  style={{ width: '100%' }}
                  placeholder="Enter new value"
                />
              </Form.Item>
              <Form.Item wrapperCol={{ span: 14, offset: 5 }}>
                <Button type="primary" htmlType="submit">Save Storage Value</Button>
              </Form.Item>
            </Form>
          </Card>
        </Col>
      </Row>
    </Layout>
  );
}

export default App;

Import Dependencies

The first few lines in App.js import React and other modules from React packages so that they can be used within the file:

import React, { useEffect, useState } from 'react';

We import Ant Design and its components:

import 'antd/dist/antd.css';
import { Card, Form, InputNumber, Button, Layout, Row, Col, Divider } from 'antd';

We import web3 so that we can call smart contracts deployed in a blockchain:

import Web3 from 'web3';

In order to interact with the SimpleStorage smart contract, we have the SimpleStorageArtifact object imported from the SimpleStorage.json file located in the contracts directory:

import SimpleStorageArtifact from './contracts/SimpleStorage.json';

Initialize Web3 Instance

In general, a client app needs to interact with smart contracts deployed in a blockchain. This is done through the web3.js library of components. As described here “The Web3 class is an umbrella package to house all Ethereum related modules”.

When we use web3 in our code segment, web3 functions like a (blockchain) provider. In the following code segment, we initialize a web3 instance of a web3 provider from one of two sources: a browser via MetaMask or a local blockchain 127.0.0.1:7545.:

const web3 = new Web3(Web3.givenProvider || 'http://127.0.0.1:7545');

With this web3 provider, we instantiate a SimpleStorage smart contract instance which we can use within the App.js file to refer to the deployed SimpleStorage smart contract. For this, we need the deployed smart contract address and abi.

const simpleStorageAbi = SimpleStorageArtifact.abi;
const simpleStorageAddress = SimpleStorageArtifact.networks[5777].address;
const SimpleStorage = new web3.eth.Contract(simpleStorageAbi, simpleStorageAddress);

Note that we access the contract abi by SimpleStorageArtifact.abi and the deployed smart contract address by SimpleStorageArtifact.networks[5777].address. We use 5777 as 5777 is the Network ID of the Ganache blockchain. Different Etheruem networks have different Network IDs. If we deploy our smart contract to a different network, we should use its Network ID instead of 5777.

ReactApp States

There is the concept of states in a React component. States are used to hold information in a React component. Note that we define App as a React component. useState hooks are used to maintain states in the App component. useState returns a pair: the current state value and a function that lets you update it.

const [ currentValue, setCurrentValue ] = useState('');

currentValue holds the current state value of App. As App corresponds to only the SimpleStorage smart contract in this project, the state value is the storedData variable’s value in the deployed SimpleStorage smart contract.

Get Current Value from Smart Contract

We read the value of the storedData variable in the smart contract using the web3 SimpleStorage smart contract instance. We access functions in the SimpleStorage smart contract using SimpleStorage.methods. In the following code snippet, we call the get() method of the SimpleStorage smart contract. Since this is a view function, we use call() method to call get() function.

SimpleStorage.methods.get().call();

Note

View functions do not modify the blockchain state.

We define an asynchronus function to get the value from the deployed smart contract. This means that when we call the function, we do not wait for it to complete, we move on to do other things. When the function returns the value obtained from the smart contract, the return value is set as the currentValue.

const getCurrentValue = async () => {
    // Get the value from the contract.
    const response = await SimpleStorage.methods.get().call();

    // Update current value with the result.
    setCurrentValue(response);
}

Note that () => { . . . } is the arrow function in Javascript. It is a short form notation for representing simple functions (with no function names) that are not doing complex things. In this case, a get value and set value statement.

UseEffect Hook

In React we use useEffect hooks to adds the ability to perform side effects from a function component. More info here.

We set the currentValue when the client application (WebApp) is loaded the first time. This is like a side effect of application loading. To achieve this, we use the useEffect hook.

Whenever the page reloads (changes), we execute the getCurrentValue function:

useEffect(() => {
    getCurrentValue();
}, [])

The useEffect method is executed whenever the page reloads.

React Form

We need a Form component to display the currentValue and allowing users to change it. The submitButtonHandler function is called when the form is submitted.

<Form
        ...
        onFinish={submitButtonHandler}
>
        <Form.Item label="Current value">
                <span>{currentValue}</span>
        </Form.Item>
        ...
</Form>

In this project, we use some Ant Design Components to build the form. As shown in the above code snippet, it displays the currentValue fetched from the deployed smart contract at the top part of the form.

The later part of the form has an input field and a button to submit new value.

<Form ... >
        ...
        <Form.Item label="Value" name="storageValue" rules={[{ required: true, message: 'Please enter value!' }]} >
                <InputNumber
                        min="0"
                        max="999999999999999"
                        style={{ width: '100%' }}
                        placeholder="Enter new value"
                />
        </Form.Item>
        <Form.Item wrapperCol={{ span: 14, offset: 5 }}>
                <Button type="primary" htmlType="submit">Save Storage Value</Button>
        </Form.Item>
</Form>

We define the numeric input field using the InputNumber component from Ant Design. The name of this numeric input field is storageValue. We can access the value of this numeric field by this name. We will discuss more on the value submission in the next section.

Change Simple Storage Contract Value

We access the Ethereum wallet addresses (in Ganache) using the following code.

const accounts = await window.ethereum.enable();

As described in the previous sections, we access functions in the SimpleStorage smart contract using SimpleStorage.methods. Now we use the set() method to set the storedData value in the smart contract:

SimpleStorage.methods.set(<new value>).send({ from: accounts[0] });
  • set(values.storageValue): We pass <new value> as a parameter to the set method as defined in the SimpleStorage smart contract. The set method changes the smart contract state; it changes the storedData value in the blockchain.

  • This type of functions create new transactions in the blockchain. The send(...) method call smart contract functions that change the smart contract state.

  • {from: accounts[0]} : The send method requires an object parameter that contains transaction details. We need to specify the from account in the send method. We pass an active MetaMask-connected account in the browser as the from account.

The final version of the submitButtonHandler function is as follows.

const submitButtonHandler = async (values) => {
    const accounts = await window.ethereum.enable();
    // Stores input value in smart contract.
    await SimpleStorage.methods.set(values.storageValue).send({ from: accounts[0] });

    // Get the value from the contract to prove it worked.
    getCurrentValue();
};

submitButtonHandler takes values as a parameter. This function will be triggered when user click the Save Storage Value button in the form. The values object contains all the field values and we can access them using their name. values.storageValue returns the value of the InputNumber field in our form. This value will be passed as the parameter to the smart contract set method.

We use the getCurrentValue function at the end of the submitButtonHandler function to load the new value from the smart contract.

Module Export

Just as there are import statements at the beginning of App.js, there is an export statement as the last line of App.js. This modularizes App.js even more clearly by expressively specifying its inputs and outputs.:

export default App;

The export exposes the App() function publicly to anything that imports it. In fact, this function is imported by client/src/index.js.