DEV Community

Cover image for Create a todo app with thirdweb deploy and Next.js
Avneesh Agarwal for thirdweb

Posted on • Originally published at blog.thirdweb.com

Create a todo app with thirdweb deploy and Next.js

This guide will 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. Simply put, you can write your smart contract, deploy it, 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
Enter fullscreen mode Exit fullscreen mode

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

npx thirdweb create --contract
Enter fullscreen mode Exit fullscreen mode

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

Now you have a hardhat project ready to go!

Writing 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 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 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();
    }
}
Enter fullscreen mode Exit fullscreen mode

This smart contract allows you to add todos to your contract, remove them, get their length and 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
Enter fullscreen mode Exit fullscreen mode

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.

image.png

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!

image.png

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.

image.png

  • 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.

image.png

  • 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.

image.png

  • In the source section, you can see your smart contract and it also gives you a verification button to the relevant chain to which you have deployed your contract.

image.png

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 create --next --ts
Enter fullscreen mode Exit fullscreen mode

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
  • 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 { ChainId, ThirdwebProvider } from "@thirdweb-dev/react";

// This is the chainId your dApp will work on.
const activeChainId = ChainId.Goerli;

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

export default MyApp;
Enter fullscreen mode Exit fullscreen mode

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();
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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("");
Enter fullscreen mode Exit fullscreen mode

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

image.png

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

image.png

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");
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

You will now be able to see all the todos 🎉

image.png

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>
                ))}
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

Now, import it in _app.tsx like this:

import "../styles/globals.css";
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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

image.png

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.

Oldest comments (2)

Collapse
 
gilfoyle profile image
Gilfoyle

I don't know what's the advantage of creating a web3 app if you are not using authentication?

Collapse
 
avneesh0612 profile image
Avneesh Agarwal • Edited

This guide was just an example to show how thirdweb deploy works and how you can write custom contracts with it and use it with our react sdk. This Dapp can be extended and you can use it to build anything you want and it does include connect wallet if that is what you mean