Blockchain React TodoList App Part-2

In this part we will see how we can create list ,create tasks and crud perform operations.

Step-1 For displaying list make sure our TodoList Smart contract backend is deployed on Blockchain network . (You may refer smart contract backend app solution in previous tutorial here )

If you haven’t deployed let’s deployed first . In my previous tutorial i have created todolist smart contract . So let’s open app and deploy smart contract locally by running below cmd.

truffle migrate --reset

Step-2 To test contract deployed and load blockchain data run below cmd.

npm run dev

If you see loading message in window browser .That’s because we’re not logged in to the blockchain yet! In order to connect to the blockchain, we need to import one of the accounts from Ganache into Metamask. (refer here to import accounts from ganache into metamask)

Step-3 Once you are connected you will be able to see below screen ,you may test to add new items.

Step-4 If you are able to see above screen means todolist contract is deployed to blockchain network. Now to interact react app with Todolist deployed smart contract we need below information .

The smart contract ABI – this a JSON description of how the smart contract behaves. It describes its functions, behavior, etc.
The smart contract address – this will be the address of the smart contract deployed to Ganache, the personal blockchain network. Make sure you deployed the smart contract to the blockchain already.

You can find smart contract abi in build folder smart contract json file. Smart contract json file create after building contract .

Smart contract address can be found after deploying smart contract to blockchain as given below.

Step-5 Let’s import ABI and smart contract address information in react app. We will create one config.js file in src folder by using below cmd.

$ touch src/config.js

Below is my config.js code with contract address and abi

export const TODO_LIST_ADDRESS = '0xaa70582ac19f93A1554d8E4Ba563b9153a9cE573'
export const TODO_LIST_ABI = [
    {
      "inputs": [],
      "payable": false,
      "stateMutability": "nonpayable",
      "type": "constructor"
    },
    {
      "anonymous": false,
      "inputs": [
        {
          "indexed": false,
          "internalType": "uint256",
          "name": "id",
          "type": "uint256"
        },
        {
          "indexed": false,
          "internalType": "bool",
          "name": "completed",
          "type": "bool"
        }
      ],
      "name": "TaskCompleted",
      "type": "event"
    },
    {
      "anonymous": false,
      "inputs": [
        {
          "indexed": false,
          "internalType": "uint256",
          "name": "id",
          "type": "uint256"
        },
        {
          "indexed": false,
          "internalType": "string",
          "name": "content",
          "type": "string"
        },
        {
          "indexed": false,
          "internalType": "bool",
          "name": "completed",
          "type": "bool"
        }
      ],
      "name": "TaskCreated",
      "type": "event"
    },
    {
      "constant": true,
      "inputs": [],
      "name": "taskCount",
      "outputs": [
        {
          "internalType": "uint256",
          "name": "",
          "type": "uint256"
        }
      ],
      "payable": false,
      "stateMutability": "view",
      "type": "function"
    },
    {
      "constant": true,
      "inputs": [
        {
          "internalType": "uint256",
          "name": "",
          "type": "uint256"
        }
      ],
      "name": "tasks",
      "outputs": [
        {
          "internalType": "uint256",
          "name": "id",
          "type": "uint256"
        },
        {
          "internalType": "string",
          "name": "content",
          "type": "string"
        },
        {
          "internalType": "bool",
          "name": "completed",
          "type": "bool"
        }
      ],
      "payable": false,
      "stateMutability": "view",
      "type": "function"
    },
    {
      "constant": false,
      "inputs": [
        {
          "internalType": "string",
          "name": "_content",
          "type": "string"
        }
      ],
      "name": "createTask",
      "outputs": [],
      "payable": false,
      "stateMutability": "nonpayable",
      "type": "function"
    },
    {
      "constant": false,
      "inputs": [
        {
          "internalType": "uint256",
          "name": "_id",
          "type": "uint256"
        }
      ],
      "name": "toggleCompleted",
      "outputs": [],
      "payable": false,
      "stateMutability": "nonpayable",
      "type": "function"
    }
  ]

Step-6 Now let’s import config.js in App.js to intract by using below cmd

import { TODO_LIST_ABI, TODO_LIST_ADDRESS } from './config'

Step-7 Now we have everything we need to connect to our smart contract. We can connect to the todo list smart contract inside the loadBlockchainData() function and Next, we set the default state in the component and then Next, we list the tasks out inside the render() function. So final app.js looks as below.

import React, { Component } from 'react'
import Web3 from 'web3'
import './App.css'
import { TODO_LIST_ABI, TODO_LIST_ADDRESS } from './config'

class App extends Component {
  componentWillMount() {
    this.loadBlockchainData()
  }
  async loadBlockchainData() {
    const web3 = new Web3(Web3.givenProvider || "http://localhost:8545")
    const accounts = await web3.eth.getAccounts()
    this.setState({ account: accounts[0] })
    const todoList = new web3.eth.Contract(TODO_LIST_ABI, TODO_LIST_ADDRESS)
    this.setState({ todoList })
    const taskCount = await todoList.methods.taskCount().call()
    this.setState({ taskCount })
    for (var i = 1; i <= taskCount; i++) {
      const task = await todoList.methods.tasks(i).call()
      this.setState({
        tasks: [...this.state.tasks, task]
      })
    }
}

constructor(props) {
  super(props)
  this.state = {
    account: '',
    taskCount: 0,
    tasks: []
  }
}
  

  render() {
    return (
      <div>
        <nav className="navbar navbar-dark fixed-top bg-dark flex-md-nowrap p-0 shadow">
          <a className="navbar-brand col-sm-3 col-md-2 mr-0" href="http://www.dappuniversity.com/free-download" target="_blank">Dapp University | Todo List</a>
          <ul className="navbar-nav px-3">
            <li className="nav-item text-nowrap d-none d-sm-none d-sm-block">
              <small><a className="nav-link" href="#"><span id="account"></span></a></small>
            </li>
          </ul>
        </nav>
        <div className="container-fluid">
          <div className="row">
            <main role="main" className="col-lg-12 d-flex justify-content-center">
              <div id="loader" className="text-center">
                <p className="text-center">Loading...</p>
              </div>
              <div id="content">
                <form>
                  <input id="newTask" type="text" className="form-control" placeholder="Add task..." required />
                  <input type="submit" hidden="" />
                </form>
                <ul id="taskList" className="list-unstyled">
                  { this.state.tasks.map((task, key) => {
                    return(
                      <div className="taskTemplate" className="checkbox" key={key}>
                        <label>
                          <input type="checkbox" />
                          <span className="content">{task.content}</span>
                        </label>
                      </div>
                    )
                  })}
                </ul>
                <ul id="completedTaskList" className="list-unstyled">
                </ul>
              </div>
            </main>
          </div>
        </div>
      </div>
    );
  }
}

export default App;

Step-7 Update styles in our src/App.css file with some custom styles.

main {
    margin-top: 60px;
  }
  
  form {
    width: 350px;
    margin-bottom: 10px;
  }
  
  ul {
    margin-bottom: 0px;
  }
  
  #completedTaskList .content {
    color: grey;
    text-decoration: line-through;
  }

Step-8 Now you should see tasks listed on the page in your web browser.

Step-9 Now let’s create new tasks with our todo list smart contract from.First, we’ll start by creating a new component to manage the todo list .

$ touch src/TodoList.js

Step-10 Now, let’s add some code for this todo list component. 

import React, { Component } from 'react'

class TodoList extends Component {

  render() {
    return (
      <div id="content">
        <form onSubmit={(event) => {
          event.preventDefault()
          this.props.createTask(this.task.value)
        }}>
          <input id="newTask" ref={(input) => this.task = input} type="text" className="form-control" placeholder="Add task..." required />
          <input type="submit" hidden={true} />
        </form>
        <ul id="taskList" className="list-unstyled">
          { this.props.tasks.map((task, key) => {
            return(
              <div className="taskTemplate" className="checkbox" key={key}>
                <label>
                  <input type="checkbox" />
                  <span className="content">{task.content}</span>
                </label>
              </div>
            )
          })}
        </ul>
        <ul id="completedTaskList" className="list-unstyled">
        </ul>
      </div>
    );
  }
}

export default TodoList;

In above code we have created onsubmit handler. Whenever new task is added from the browser createTask() method is called .

Step-11 So let’s define createtask method in App.js

createTask(content) {
    this.setState({ loading: true })
    this.state.todoList.methods.createTask(content).send({ from: this.state.account })
    .once('receipt', (receipt) => {
      this.setState({ loading: false })
})

Step-12 Now, let’s bind the function like this inside the constructor() function.

this.createTask = this.createTask.bind(this)

Step-13 Now render out the todo list component, passing the newly created function down via props .

<main role="main" className="col-lg-12 d-flex justify-content-center">
  { this.state.loading
    ? <div id="loader" className="text-center"><p className="text-center">Loading...</p></div>
    : <TodoList tasks={this.state.tasks} createTask={this.createTask} />
  }
</main>

Step-14 Finally, we can keep track of the loading state by first setting default state in the constructor() function:

constructor(props) {
    super(props)
    this.state = {
      account: '',
      taskCount: 0,
      tasks: [],
      loading: true
}

Then, set it to false whenever the blockchain data is loaded from the loadBlockchainData() function.

this.setState({ loading: false })

Step- 15 We can toggle tasks as completed by triggering an onClick event for the task’s checkbox that will call a toggleCompleted() function. So let’s define toggleCompleted() function in input checkbox and call the function.

<input
  type="checkbox"
  name={task.id}
  defaultChecked={task.completed}
  ref={(input) => {
    this.checkbox = input
  }}
  onClick={(event) => {
    this.props.toggleCompleted(this.checkbox.name)
}}/>

Step-16 Call toggleCompleted() function and bind in constructor.

toggleCompleted(taskId) {
    this.setState({ loading: true })
    this.state.todoList.methods.toggleCompleted(taskId).send({ from: this.state.account })
    .once('receipt', (receipt) => {
      this.setState({ loading: false })
    })
}
this.toggleCompleted = this.toggleCompleted.bind(this)

Step 17 – After binding toggleCompleted() app.js will look like this .

import React, { Component } from 'react'
import Web3 from 'web3'
import './App.css'
import { TODO_LIST_ABI, TODO_LIST_ADDRESS } from './config'
import TodoList from './TodoList'

class App extends Component {
  componentWillMount() {
    this.loadBlockchainData()
  }

  async loadBlockchainData() {
    const web3 = new Web3(Web3.givenProvider || "http://localhost:8545")
    const accounts = await web3.eth.getAccounts()
    this.setState({ account: accounts[0] })
    const todoList = new web3.eth.Contract(TODO_LIST_ABI, TODO_LIST_ADDRESS)
    this.setState({ todoList })
    const taskCount = await todoList.methods.taskCount().call()
    this.setState({ taskCount })
    for (var i = 1; i <= taskCount; i++) {
      const task = await todoList.methods.tasks(i).call()
      this.setState({
        tasks: [...this.state.tasks, task]
      })
    }
    this.setState({ loading: false })
  }

  constructor(props) {
    super(props)
    this.state = {
      account: '',
      taskCount: 0,
      tasks: [],
      loading: true
    }

    this.createTask = this.createTask.bind(this)
    this.toggleCompleted = this.toggleCompleted.bind(this)
  }

  createTask(content) {
    this.setState({ loading: true })
    this.state.todoList.methods.createTask(content).send({ from: this.state.account })
    .once('receipt', (receipt) => {
      this.setState({ loading: false })
    })
  }

  toggleCompleted(taskId) {
    this.setState({ loading: true })
    this.state.todoList.methods.toggleCompleted(taskId).send({ from: this.state.account })
    .once('receipt', (receipt) => {
      this.setState({ loading: false })
    })
  }

  render() {
    return (
      <div>
        <nav className="navbar navbar-dark fixed-top bg-dark flex-md-nowrap p-0 shadow">
          <a className="navbar-brand col-sm-3 col-md-2 mr-0" href="#" target="_blank">Blockchain React Todo List</a>
          <ul className="navbar-nav px-3">
            <li className="nav-item text-nowrap d-none d-sm-none d-sm-block">
              <small><a className="nav-link" href="#"><span id="account"></span></a></small>
            </li>
          </ul>
        </nav>
        <div className="container-fluid">
          <div className="row">
            <main role="main" className="col-lg-12 d-flex justify-content-center">
              { this.state.loading
                ? <div id="loader" className="text-center"><p className="text-center">Loading...</p></div>
                : <TodoList
                  tasks={this.state.tasks}
                  createTask={this.createTask}
                  toggleCompleted={this.toggleCompleted} />
              }
            </main>
          </div>
        </div>
      </div>
    );
  }
}

export default App;

and TodoList.json

import React, { Component } from 'react'

class TodoList extends Component {

  render() {
    return (
      <div id="content">
        <form onSubmit={(event) => {
          event.preventDefault()
          this.props.createTask(this.task.value)
        }}>
          <input
            id="newTask"
            ref={(input) => {
              this.task = input
            }}
            type="text"
            className="form-control"
            placeholder="Add task..."
            required />
          <input type="submit" hidden={true} />
        </form>
        <ul id="taskList" className="list-unstyled">
          { this.props.tasks.map((task, key) => {
            return(
              <div className="taskTemplate" className="checkbox" key={key}>
                <label>
                  <input
                  type="checkbox"
                  name={task.id}
                  defaultChecked={task.completed}
                  ref={(input) => {
                    this.checkbox = input
                  }}
                  onClick={(event) => {
                    this.props.toggleCompleted(this.checkbox.name) }}/>
                  <span className="content">{task.content}</span>
                </label>
              </div>
            )
          })}
        </ul>
        <ul id="completedTaskList" className="list-unstyled">
        </ul>
      </div>
    );
  }
}

export default TodoList;

Source code can be found here .

Leave a Reply

Your email address will not be published. Required fields are marked *