Create a todo app with thirdweb deploy and Next.js

Create a todo app with thirdweb deploy and Next.js - thirdweb Guides

⚠️ Warning: This guide currently uses v4 of the Connect SDK. For v5 (latest) code snippets, please check out our documentation while this guide is being updated. ⚠️

In this guide, we'll show you how to build a full web3 application that allows users to create an on-chain to-do list, using Solidity for the smart contract and Next.js for the application.

Before we get started, below are some helpful resources where you can learn more about the tools we're going to be using in this guide.

Let's get started!

Creating the Smart Contract

To build the smart contract we will be using Hardhat.

Hardhat is an Ethereum development environment and framework designed for full stack development in Solidity. In simple words, you can write your smart contract, deploy them, run tests, and debug your code.

Setting up a new hardhat project

Create a folder where the hardhat project and the Next.js app will go. To create a folder, open up your terminal and execute these commands

mkdir todo-dapp
cd todo-dapp

Now, we will use the thirdweb CLI to generate a new hardhat project! So, run this command:

npx thirdweb@latest create contract

When it asks for what type of project, you need to select an empty project!

Now you have a hardhat project ready to go!

Creating the smart contract

Once the app is created, create a new file inside the contracts directory called Todo.sol and add the following code:

// SPDX-License-Identifier: MIT

// specify the solidity version here
pragma solidity ^0.8.0;

contract Todos {
    // We will declare an array of strings called todos
    string[] public todos;
    
    // We will take _todo as an input and push it inside the array in this function
    function setTodo(string memory _todo) public {
        todos.push(_todo);
    }

    // In this function we are just returning the array
    function getTodo() public view returns (string[] memory) {
        return todos;
    }
    
    // Here we are returning the length of the todos array
    function getTodosLength() public view returns (uint) {
        uint todosLength = todos.length;
        return todosLength;
    }
    
    // We are using the pop method to remove a todo from the array as you can see we are basically just removing one index
    function deleteToDo(uint _index) public {
        require(_index < todos.length, "This todo index does not exist.");
        todos[_index] = todos[getTodosLength() - 1];
        todos.pop();
    }
}

This smart contract allows you to add todos to your contract, remove them, get their length and also return the array which consists of all the tasks you have set up.

Now that we have written our basic Todos smart contract, we will go ahead and deploy our contract using deploy.

Deploying the contract

npx thirdweb deploy

This command allows you to avoid the painful process of setting up your entire project like setting up RPC URLs, exporting private keys, and writing scripts.

Upon success, a new tab will automatically open and you should be able to see a link to the dashboard in your CLI.

use npx thirdweb deploy to deploy the contract

Now, choose the network you want to deploy your contract to! I am going to use Goerli but you can use whichever one you like. Once you have chosen your network click on Deploy now!

Select the contract you want to deploy to and click deploy

After the transactions are mined you will be taken to the dashboard which consists of many options.

  • In the overview section, you can explore your contract and interact with the functions without having to integrate them within your frontend code yet so it gives you a better idea of how your functions are working and also acts as a good testing environment.
Contract Explorer
  • In the code section, you see the different languages and ways you can interact with your contract. Which we will look into later on in the tutorial.
Code for using the SDKs
  • In the events section, you can see all the transactions you make.
  • You can also customize the settings after enabling the required interfaces in the settings section.
Contract Settings
  • In the source section, you can see your smart contract and it also gives you a verification button to the relevant chain you have deployed your contract to.
Source of the contract

Creating the Frontend

I am going to use the Next.js Typescript starter template for this guide.

If you are following along with the guide, you can create a project with the template using the thirdweb CLI:

npx thirdweb@latest create app

If you already have a Next.js app you can simply follow these steps to get started:

  • Install @thirdweb-dev/react and @thirdweb-dev/sdk and ethers@5
  • Add MetaMask authentication to the site. You can follow this guide to do this.

By default the network is Mainnet, we need to change it to Goerli

import type { AppProps } from "next/app";
import { ThirdwebProvider } from "@thirdweb-dev/react";
import "../styles/globals.css";

// This is the chain your dApp will work on.
// Change this to the chain your app is built for.
// You can also import additional chains from `@thirdweb-dev/chains` and pass them directly.
const activeChain = "goerli";

function MyApp({ Component, pageProps }: AppProps) {
  return (
    <ThirdwebProvider activeChain={activeChain}>
      <Component {...pageProps} />
    </ThirdwebProvider>
  );
}

export default MyApp;

Create todo functionality

Let's now go to pages/index.tsx and firstly, get the wallet address of the user like this:

const address = useAddress();

Then, we are going to check if the address exists and if it does we are going to show a simple input and button to create a new todo:

<div>
  {address ? (
    <div>
      <input
        value={input}
        onChange={(e) => setInput(e.target.value)}
        placeholder="Enter todo"
      />

      <Web3Button
        contractAddress={contractAddress}
        action={(contract) => contract.call("setTodo", input)}
      >
        Set Todo
      </Web3Button>
    </div>
  ) : (
    <ConnectWallet  />
  )}
</div>

We are using a state to store the value of a state variable and I have also created a variable for contractAddress as it will be used in multiple places:

  const contractAddress = "0x80ddA9989F272BFB1c53c1A100ff118Fd27dDb59";
  const [input, setInput] = useState("");

To get the contract address go to your contract on thirdweb and copy the contract

Copy contract address

If you now check out the app, you will be able to add todos!

Add todo

Read todos functionality

Using the useContract and useContractData hooks we will get the todos like this:

  const { contract } = useContract(contractAddress);
  const { data, isLoading } = useContractData(contract, "getTodo");

Now, we will check if the todos are loading and if it is loading we will show a loading screen otherwise we will show the todos:

  <div>
            {isLoading ? (
              "Loading..."
            ) : (
              <ul>
                {data.map((item: string, index: number) => (
                  <li key={index}>
                    {item}
                  </li>
                ))}
              </ul>
            )}
          </div>

You will now be able to see all the todos 🎉

View all the todos

Adding delete functionality

Where we are mapping through all the todos we will add another Web3Button that will be responsible for deleting the todos:

  {data.map((item: string, index: number) => (
                  <li key={index}>
                    {item}
                    <Web3Button
                      contractAddress={contractAddress}
                      action={(contract) => contract.call("deleteToDo", index)}
                    >
                      Delete Todo
                    </Web3Button>
                  </li>
                ))}

Adding some styles

We will add some basic styles to our app so it looks good! So, create a globals.css file in the styles folder and add the following:

html,
body {
  padding: 0;
  margin: 0;
  font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, Ubuntu,
    Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
}

a {
  color: inherit;
  text-decoration: none;
}

* {
  box-sizing: border-box;
}


Now, import it in _app.tsx like this:

import "../styles/globals.css";

Let's style the home page now! Create a file Home.module.css and add the following:

.container {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  width: 100vw;
  min-height: 100vh;
  background-color: #c0ffee;
}

.todo {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 20px;
  margin-top: 10px;
}

.todo > span > button {
  border: none;
  height: 30px !important;
}

.todoForm {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-top: 10px;
  gap: 10px;
}

.todoForm > span > button {
  border: none;
  height: 20px !important;
}


Finally, add the classNames:

import {
  ConnectWallet,
  useAddress,
  useContract,
  useContractData,
  Web3Button,
} from "@thirdweb-dev/react";
import type { NextPage } from "next";
import { useState } from "react";
import styles from "../styles/Home.module.css";

const Home: NextPage = () => {
  const address = useAddress();
  const contractAddress = "0x80ddA9989F272BFB1c53c1A100ff118Fd27dDb59";
  const [input, setInput] = useState("");
  const { contract } = useContract(contractAddress);
  const { data, isLoading } = useContractData(contract, "getTodo");

  return (
    <div className={styles.container}>
      {address ? (
        <>
          <div className={styles.todoForm}>
            <input
              value={input}
              onChange={(e) => setInput(e.target.value)}
              placeholder="Enter todo"
            />

            <Web3Button
              contractAddress={contractAddress}
              action={(contract) => contract.call("setTodo", input)}
              accentColor="#1ce"
            >
              Set Todo
            </Web3Button>
          </div>

          <div>
            {isLoading ? (
              "Loading..."
            ) : (
              <ul>
                {data.map((item: string, index: number) => (
                  <li key={index} className={styles.todo}>
                    {item}
                    <Web3Button
                      contractAddress={contractAddress}
                      action={(contract) => contract.call("deleteToDo", index)}
                      accentColor="#1ce"
                    >
                      Delete Todo
                    </Web3Button>
                  </li>
                ))}
              </ul>
            )}
          </div>
        </>
      ) : (
        <ConnectWallet accentColor="#1ce" colorMode="light" />
      )}
    </div>
  );
};

export default Home;

Now, we have an awesome web3 to-do app ready! 🥳

Our final web3 todo app

Conclusion

In this guide, we've learned how to build a full-stack web3 todo app! If you did as well pat yourself on the back and share it with us on the thirdweb discord.