Build a CRUD DApp using Solidity | Nextjs | Thirdweb | Tailwind CSS
Tags: web3, nextjs, react, javascript
Published: 2023-03-04 21:56:36

Hey there, today we are going to be learning how to create the **CRUD operations** in **Web3** by building a simple Blog DApp. In this tutorial we will: 

1. Build a **Solidity** smart contract that will hold the blog's content and allow users to interact with it.
2. Deploy the smart contract to the Mumbai TestNet, and perform the Web3 interactions on the Frontend using **Thirdweb**.
3. Build the frontend using **Nextjs**.
4. Style our blog using **Tailwind CSS**.

By the end of that tutorial, you'll have a full-stack CRUD DApp, that you can build your Web3 knowledge upon, whether you are new to the technology, or just need to refresh your basics.

Without further ado, let's dive right in and get started!

Let's start by opening up the terminal and creating a new directory for our project

`mkdir Next3Blog && cd Next3Blog`


Here we are calling it **Next3Blog** but you can call it whatever you want.

After that, we are going to create a new Thirdweb project, that will contain our smart contract logic

Run the following command

`npx thirdweb@latest create --contract`

It should prompt a few questions that will help setup the environment based on our needs


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9papl5hyslairduilo4u.png)

I set the project name to **web3**, picked **Hardhat** as our development environment framework, set **BlogContract** as the name of our smart contract, and finally, chose to initiate the project with an **empty contract**.

Navigate to the newly generated folder and take a look at the directory structure

`cd web3`

After navigating to the project folder, you will find the contracts directory which contains a single smart contract.

## Building a Smart Contract using Solidity:

**Solidity** is a powerful high-level programming language. It's very similar to **JavaScript**, but it's essentially designed and used to write smart contracts on the Ethereum blockchain.

Open the contract file and copy the following code into it

```
//SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;


contract BlogContract {
   struct Post {
        uint postId;
        address owner;
        string postContent;
        bool isDeleted;
    }

    mapping(uint => address) blogOwners;
    Post[] private posts;
}

```

We start by specifying the compiler version for the smart contract.

Then, we declare Post struct as a custom data type for our blog posts. It contains four variables, postId as uint which represents an unsigned (positive) integer, postContent as a string, isDeleted as a boolean.

`mapping` is a hash table that allows us to associate a key with a value, so in the above code we define a blogOwners mapping of a unit key, and address value.


After that, we declare a private posts array of type Post. The private keyword makes the array inaccessible from outside the contract.

## Adding posts:

```
  function addPost(string memory postContent) external {
       uint postId = posts.length;
       address owner = msg.sender;
       posts.push(Post(postId, owner, postContent, false));
       blogOwners[postId] = owner;
   }

```


In Solidity, a function parameter must be declared with a specific data type since it's a typed language. 

In the above code, we are defining the addPost function, with a string parameter. `memory` is used to store data temporarily during the execution of a function, and will be deleted from memory once the function returns.

`external` makes the function visible from outside the contract only, and cannot be called from within the contract itself.

We then assign a couple of variables, postId of type unit, and owner of type address. 

`msg.sender` refers to the address that called the function.

After that, we use the Post struct to define the post variables and push it to the posts array. And finally, we map the postId to the blog owner address.

In Solidity, `events` are used as a way to notify external systems or contracts about specific actions that occur within a contract. 

Let's assign the **AddPost** event that will serve as a notifier when a new post is registered.


```
contract BlogContract {
   event AddPost(address owner, uint postId);
   ...
}

```

Now we add emit to the **addPost** function to trigger the event.

```
function addPost(string memory postContent) external {
       ...
       emit AddPost(owner, postId);
}
```

## Deleting posts:

```
function deletePost(uint postId) external {
       require(blogOwners[postId] == msg.sender, "You are not the owner of this post");
       posts[postId].isDeleted = true;
   }

```

Here we are defining a function that takes a single parameter postId of type uint, and then checking if the caller address is the owner of the post. `require` checks for the validity of the condition and it will revert the transaction and display the error message that we set.

Once data is written to the blockchain, **it can’t be deleted** as it becomes a permanent part of the blockchain’s history. However, we can "soft-delete" it. Remember the variable isDeleted that we added above? We are initially assigning it to false, and we will simply modify it to be true, which will create an illusion of deleting it. 

Later on, when implementing the **getPosts** function we will filter the data, and only return data with the isDeleted set to false.

Let's now declare the DeletePost event

```

contract BlogContract {
   ...
   event DeletePost(uint postId, bool isDeleted);
   ...

   function deletePost(uint postId) external {
      ...
      emit DeletePost(postId, true);
   }

}
```

## Getting posts:

```
function getPosts() external view returns (Post[] memory){
        Post[] memory temporary = new Post[](posts.length);
        uint counter = 0;
        for (uint i = 0; i < posts.length; i++) {
            if (posts[i].isDeleted == false) {
                temporary[counter] = posts[i];
                counter++;
            }
        }
        Post[] memory result = new Post[](counter);
        for (uint i = 0; i < counter; i++) {
            result[i] = temporary[i];
        }
        return result;
    }
```


A `view` function allows read-only access to the data stored in the contract and since it doesn't modify the state of the contract or blockchain, it doesn't require any transaction to be sent to the blockchain making it a lightweight operation.

As I mentioned earlier, we are gonna loop through the posts array, and only return posts with the isDeleted set to false.

First, we declare a temporary array of type Post, with a size equal to the length of the posts array.

Note that when declaring an array with the memory keyword, you need to specify the size of the array because Solidity doesn't support dynamic array resizing in memory.

Then we assign a counter variable that will track the number of the non "deleted" posts.


Then we loop through the posts array and store the non deleted posts inside the temporary array.

Since the temporary array may still have extra slots for the deleted posts, we need to declare another array of length counter to ensure that the final result array is of the exact size of the non-deleted posts.



Now that we finished implementing the contract, we need to deploy it to the blockchain. In the project directory you will find `hardhat.config.js`, this file contains all of our configuration settings of our development environment.

Navigate to the file and copy the code below into it:

```
module.exports = {
  solidity: {
    version: '0.8.9',
    defaultNetwork: 'mumbai',
    networks: {
      hardhat: {},
      mumbai: {
        url: 'https://rpc.ankr.com/polygon_mumbai',
        accounts: [`0x${process.env.PRIVATE_KEY}`]
      }
    },
    settings: {
      optimizer: {
        enabled: true,
        runs: 200,
      },
    },
  },
};
```

Here we are specifying the settings for the Hardhat project.

We declare the version of the compiler, the defaultNetwork as the **Mumbai Testnet** and defining the network configurations, we specify the URL of the RPC endpoint "https://rpc.ankr.com/polygon_mumbai"  and the account to use for deployment.

`optimizer` is a tool that tries to reduce the gas cost of a smart contract by optimizing its bytecode, and setting the optimizer to 200 means that it will run the optimizer for a maximum of 200 times. The more times the optimizer runs, the more aggressive it is in optimizing the bytecode, but it also takes longer to compile the contract.

Now head to your browser and download the [MetaMask wallet extension](https://metamask.io/download/).

Follow the instructions to create a new wallet and don't forget to save the secret recovery phrase.

After you successfully set up your wallet, head to the extension and add the Mumbai TestNet to your networks.


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kksp4trp4fx6kgpc5r32.png)

1. Network name: Mumbai TestNet
2. New RPC URL: https://rpc.ankr.com/polygon_mumbai
3. Chain ID: 80001
4. Currency symbol: MATIC

Now copy the private key as shown here. That will give access to the wallet account and enable us to deploy the contract using it



![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8kp9leewehyy4817u9i5.png)


For security purposes we need to add the private key into `.env` file

We need to install `dotenv` to be able to load and manage the environment variables from the `.env` file into the process environment.

`npm install dotenv --save`


When deploying the contract, it requires a certain amount of MATIC to cover the transaction fees.

Let's now get some MATIC faucet (https://faucet.polygon.technology/)

Head to [https://faucet.polygon.technology](https://faucet.polygon.technology/) and copy your wallet address into the input, click submit, and you should get a small amount of MATIC in a few moments.


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ypp5vltxhon93566m0yb.png)

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ekpkcu3vgl15kr3gahp6.png)

This should be sufficient to deploy our contract.

Now open your terminal and run the deployment command 

`npm run deploy`

This is a **thirdweb** script that will detect any contract inside the directory and compile it, upload the ABI to IPFS and generate a deployment link.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/y20mpelwuks1xrnig19k.png)

Open the link in your browser and click the deploy button to deploy the contract to the blockchain


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dbxxdct0k7g37atwnj5b.png)


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/h2p4z67iupgkqfu9ux42.png)


Thirdweb will generate a dashboard to manage and interact with the deployed contract conveniently. Copy the contract address and put it to the side now, we are gonna use it later when we move on to the Frontend part.


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yr7ijgigr74lbyduub4t.png)

Navigate to **Explorer** and play around with the contract functions


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3fxvjx6saa5nd0azv5k5.png)

## Nextjs

**Next.js** is an open-source React framework that provides server-side rendering, and makes it easier to build and optimize complex web applications.

We'll start by setting up our development environment and create a Nextjs app

Navigate to the root directory and copy the command below into your terminal

`npx create-next-app@latest client`

You will be prompted with a bunch of questions in the terminal


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/w34rczw5ucrp9qpyp7id.png)

After it finishes installing the project dependencies, navigate to the project directory.

## Tailwind CSS

**Tailwind CSS** is a utility-first CSS framework and a design system that provides you with pre-built classes to style your elements and create consistent and responsive web pages.

So, let's say you have a button element and you want to change its background color to gray, you can simply add bg-gray-500 - Assigning the value as 500 means that the color is gonna have a mid-level shade.

**Setting up Tailwind CSS:**

Let's first install the Tailwind CSS dependencies 

`npm install -D tailwindcss postcss autoprefixer`

PostCSS and autoprefixer apply additional optimizations and ensure that the generated classes work on all browsers without worrying about the compatibility issues.


Initialize Tailwind CSS in our project

`npx tailwindcss init -p`


That will generate two files npm `tailwind.config.js` and `postcss.config.js`.

Inside `tailwind.config.js` copy the code below

```
module.exports = {
  content: [
    "./src/**/*.{js,jsx}",
  ],
  theme: {
    extend: {
      fontFamily: {
        openSans: ['Open Sans', 'sans-serif']
      }
    },
  }
}
```

Here we are adding the paths to all of the expected template files to contain Tailwind styles, and the font we're gonna use in the project.

Then you navigate to **./src/styles/globals.css** and add the Tailwind directives and the link for the font.

```
@import url('https://fonts.googleapis.com/css2?family=Open+Sans:wght@300;400;600;700&display=swap');

@tailwind base;
@tailwind components;
@tailwind utilities;
```

Now we are ready to start building our UI

Here is a closer look to the final view of what we are building


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1klub6bajfpvyctzuz72.png)

In the pages directory, you will find `index.js`. This will be our main page.

In the src directory create components directory, and inside of it create four new files `Navbar.js`, `Form.js`, `Post.js`, and `index.js`.

Add a **utils** folder and inside of it create `index.js`

By now the src directory tree should look like this


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/t5migal6sj64ba8xo132.png)

Copy the code below into ./pages/index.js

```
export default function App() {

  return (
    <div className="bg-[#eff2f5] flex flex-col min-h-screen">

      

    </div>
  )
}
```
As you can see, the tailwind classes that we are adding here are self-explanatory, we are simply setting the background color to #eff2f5, the display to flex, the flex direction to flex column, and the min height to 100vh.

Let's install an additional package before we implement the Navbar

`npm i react-identicons --save`

Identicons provide a visual representation to wallet addresses. They are typically generated using an algorithm that takes the wallet address as input and produces a unique image based on that input.

Navigate to `Navbar.js` and copy the following code into it

```
import Identicon from 'react-identicons';
export default function Navbar() {
 return (
   <div className="bg-white">
       <div className="px-5 py-2 flex items-center justify-between shadow-md">
           <h2 className="font-openSans font-bold text-gray-600 font-medium">
               Next3Blog
           </h2>
           <div className="border border-[#c7cacd] text-gray-600 px-2 py-1 rounded-[12px] inline-flex items-center">
               <div className="w-8 h-8 mr-2 rounded-full overflow-hidden border border-[#c7cacd] flex justify-center ">
                   <Identicon string={"0x34710CdeC3e80A174cb384870B3Ff2854d5556ce"} size={30}/>
               </div>
               <h2 className="font-openSans text-black text-[12px]">0x34710CdeC3e80A174cb384870B3Ff2854d5556ce</h2>
           </div>   
       </div>
   </div>
 )
}

```

Notice how we are using the font that we previously declared in the `tailwind.config.js`. 

To save space, and make the UI more compact, let's shorten the wallet address that we are displaying on the Navbar. This is a convention in DApps to represent the address in a user-friendly way.

Navigate to `./utils/index.js` and add the following function

```
export const truncateAddress = (address) => {
   return address.substring(0, 6) + '...' + address.substring(address.length - 4, address.length)
};
```
This truncates the address to show the first 6 characters and the last 4 characters, with an ellipsis (...) in between.

Let's import the function in `Navbar.js` and call the function with the temporary address.

```
...               
<h2 className="font-openSans text-black text-[12px]">{truncateAddress(address)}</h2>
...

```

Next, navigate to `Form.js`

```
import Identicon from 'react-identicons';
export default function Form() {
 return (
   <form className="lg:w-1/2 flex flex-col w-full h-full bg-white overflow-hidden mb-[50px] shadow-md rounded-[8px] py-5 px-10 box-border">
       <div className="w-full inline-flex justify-between items-center mb-4">
       <div className="w-12 h-12 rounded-full overflow-hidden border border-[#c7cacd] flex justify-center ">
           <Identicon string={"0x34710CdeC3e80A174cb384870B3Ff2854d5556ce"} size={45}/>
       </div>
       <div className="w-4/5 lg:w-[90%]">
           <input type="text" id="post" className="font-openSans w-full p-3 text-md border rounded-[22px] focus:outline-none focus:shadow-outline" placeholder="Share Your Thoughts and Ideas!" />
       </div>
       </div>
       <div className="w-full justify-center items-center">
       <button className="w-full bg-[#1974E7] px-2 py-2 rounded-[6px] flex justify-center cursor-pointer disabled:opacity-50 disabled:cursor-default" type='submit'>
           <h2 className='font-openSans text-white transition'>Publish</h2>
       </button>
       </div>
   </form>
 )
}

```

**w-4/5**: Sets the width to 75%
**lg:w-[90%]**: Sets the width to 90% on large screens


Navigate to `Post.js`

```
import Identicon from 'react-identicons';
import { truncateAddress } from '@/utils';


export default function Post() {
 return (
   <div className="lg:w-1/2 flex flex-col w-full h-full bg-white overflow-hidden mb-6 shadow-md rounded-[8px] pt-2 pb-5 px-6 box-border">
       <div className="w-full inline-flex justify-between items-center mb-4">
         <div className="inline-flex items-center">
           <div className="w-[40px] h-[40px] flex justify-center rounded-full overflow-hidden flex-shrink-0 border border-[#c7cacd]">
             <Identicon string={"0x34710CdeC3e80A174cb384870B3Ff2854d5556ce"} size={35} />
           </div>
           <div className="flex-1 pl-2">
             <h2 className="font-openSans text-black mb-1">{truncateAddress("0x34710CdeC3e80A174cb384870B3Ff2854d5556ce")}</h2>
           </div>
         </div>
           <button className="w-[10px] cursor-pointer">
             <svg className="fill-current w-3 h-3" version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="122.878px" height="122.88px" viewBox="0 0 122.878 122.88" enableBackground="new 0 0 122.878 122.88" xml-space="preserve">
               <g>
                 <path d="M1.426,8.313c-1.901-1.901-1.901-4.984,0-6.886c1.901-1.902,4.984-1.902,6.886,0l53.127,53.127l53.127-53.127 c1.901-1.902,4.984-1.902,6.887,0c1.901,1.901,1.901,4.985,0,6.886L68.324,61.439l53.128,53.128c1.901,1.901,1.901,4.984,0,6.886 c-1.902,1.902-4.985,1.902-6.887,0L61.438,68.326L8.312,121.453c-1.901,1.902-4.984,1.902-6.886,0 c-1.901-1.901-1.901-4.984,0-6.886l53.127-53.128L1.426,8.313L1.426,8.313z"></path>
               </g>
             </svg>
           </button>
       </div>
       <div className="w-full justify-center items-center">
         <p className='font-openSans'>Sample Blog post</p>
       </div>
     </div>
 )
}

```

Now to better organize our components export them in the **./components/index.js** file

```
export { default as Navbar } from './Navbar';
export { default as Form } from './Form';
export { default as Post } from './Post';
```

After that, we import our components to our main page **./pages/index.js**

```
...
import { Navbar, Form, Post } from "@/components";
...
 return (
   <div className="bg-[#eff2f5] flex flex-col min-h-screen">
     <Navbar />
     <div className="pt-12 pb-6 flex flex-col items-center justify-center">
       <Form />
       <Post />
     </div>
   </div>
 )
}

```

## Thirdweb Integrations: 

Now that we are done creating the base UI for our app. Let's start the Web3 integrations to be able to interact with our contract

Install the dependencies

`npm i @thirdweb-dev/sdk @thirdweb-dev/react --save`

Navigate to **./pages/_app.js** and copy the following code into it

```
import Head from 'next/head';
import { ChainId, ThirdwebProvider } from '@thirdweb-dev/react';
import '@/styles/globals.css'


export default function App({ Component, pageProps }) {
 return (
   <ThirdwebProvider network={ChainId.Mumbai}>
     <Head>
       <title>Next3Blog</title>
     </Head>
     <Component {...pageProps} />
   </ThirdwebProvider>
 )
}

```

Here we are wrapping the app with the Thirdweb provider, and setting the network to the Mumbai chain id.


Inside **./pages/index.js** copy the following code

```
import { useMetamask, useAddress } from "@thirdweb-dev/react";
export default function App() {
 const connect = useMetamask();
 const address = useAddress();
```

We import a couple of hooks provided by Thirdweb, we're gonna need a **useMetamask** to connect the app to Metamask wallet and **useAddress** that gets the connected address


Then we pass both hooks to the **Navbar** component and address to the **Form** and **Post**.

```
...
<div className="bg-[#eff2f5] flex flex-col min-h-screen">
 <Navbar
   connect={connect}
   address={address}
 />
 <div className="pt-12 pb-6 flex flex-col items-center justify-center">
   <Form
     address={address}
   />
   <Post
     address={address}
   />
 </div>
</div>
...

```

Let's implement the connect wallet functionality in the **Navbar** 

```
export default function Navbar({ connect, address }) {
 return (
   <div className="bg-white">
       ...
       { address ?
           <div className="border border-[#c7cacd] text-gray-600 px-2 py-1 rounded-[12px] inline-flex items-center">
               <div className="w-8 h-8 mr-2 rounded-full overflow-hidden border border-[#c7cacd] flex justify-center ">
                   <Identicon string={address} size={30}/>
               </div>
               <h2 className="font-openSans text-black text-[12px]">{truncateAddress(address)}</h2>
           </div> :
           <button className="group/item border border-[#c7cacd] text-gray-600 px-2 py-1 rounded-[12px] inline-flex items-center cursor-pointer hover:bg-[#1974E7] hover:text-white transition"
               onClick={() => connect()}
           >
               <h2 className='font-openSans group-hover/item:text-white text-[#1974E7] transition'>Connect Wallet</h2>
           </button>
       }
       ...
   </div>
 )
}

```

Here we are doing conditional rendering, so we show the address if there is a connected address, or else show the connect button

Back to `index.js`

We import **useContract** to be able to connect to our contract and pass our smart contract address to it.

```
import { ..., useContract } from "@thirdweb-dev/react";
...
export default function App() {
...
const { contract } = useContract("0x1e55A7D8c854c239cD30EA737700D1C7d7273395");
```

Now let's do our first contract call

```


...
 const [posts, setPosts] = useState([]);
...
const getPosts = async () => {
   try {
     const posts = await contract.call('getPosts');
     const parsedPosts = posts.map((post, i) => ({
       owner: post.owner,
       postContent: post.postContent,
       id: post.postId.toNumber()
     }));
     setPosts(parsedPosts);
   }
   catch (err) {
     console.error("contract call failure", err);
   }
 }

```
**Thirdweb** makes it extremely simple to integrate our smart contract functions.
Here we are calling the **getPosts** function and then parsing through the returned posts array to get the values that we need. If you haven’t played around on Thirdweb dashboard and created a post, this should be returning an empty array.

Then we call the function inside useEffect to run whenever the component mounts

```
 useEffect(() => {
   if(contract) getPosts();
 }, [address, contract]);
```

we check if the contract value exists before calling **getPosts**, and re-runs whenever the address or contract values is updated.

Next, we map over the posts array to return a list of Post components.


```
{posts.map(post =>
         <Post
           key={post.id}
           id={post.id}
           owner={post.owner}
           address={address}
           postContent={post.postContent}
         />)
       }
```

Before we implement the create post functionality we need to keep track of the form input value, let’s declare a new useState variable and pass it down to the Form component

```
... 
const [text, setText] = useState('');
...
<Form
         address={address}
         setText={setText}
         text={text}
         buttonText={buttonText}
       />
...

```

In the Form component we set up the event listener that is triggered whenever the user types something into the input field

```
export default function Form({ setText, text }) {
   return (
     <form className="lg:w-1/2 flex flex-col w-full h-full bg-white overflow-hidden mb-[50px] shadow-md rounded-[8px] py-5 px-10 box-border">
          ...


         <div className="w-4/5 lg:w-[90%]">
             <input type="text" id="post" className="font-openSans w-full p-3 text-md border rounded-[22px] focus:outline-none focus:shadow-outline" placeholder="Share Your Thoughts and Ideas!"
                 value={text}
                 onChange={e=>setText(e.target.value)}
             />
         </div>
        
         ...
      </form>
   )
 }

```

Navigate back to `index.js` and let’s implement publishPost function

```
 const [publishLoading, setPublishLoading] = useState(false);
```

We declare **publishLoading** state variable to track the loading state while executing the function

```
const publishPost = async (text) => {
   if(text.length > 0 && contract){
     try {
       setPublishLoading(true);
       const data = await contract.call('addPost', text);
       if(data.receipt.status === 1){
         getPosts();
         setText('');
       }
       return data;
     } catch (err) {
       console.error("contract call failure", err);
     } finally {
       setPublishLoading(false);
     }
   }
 }

```

Here we are calling the **addPost** function and passing the input text to it, after checking if the text input is not empty and the contract exists. 

If the transaction was successful, we call the **getPosts** function, and clear out the input field.

Next, we pass the function down to the **Form** component

```
...
<Form
         ...
         publishPost={publishPost}
         ...
       />
...
```

Then we update the Form component to trigger the **publishPost** on submit

```
export default function Form({ ... text, publishPost }) {
   return (
     <form className="lg:w-1/2 flex flex-col w-full h-full bg-white overflow-hidden mb-[50px] shadow-md rounded-[8px] py-5 px-10 box-border" onSubmit={() => publishPost(text)}>
          ...
      </form>
   )
 }

```

Now we implement the **deletePost** function

```
onst deletePost = async (id) => {
   try {
     const data = await contract.call('deletePost', id);
     if(data.receipt.status === 1){
       getPosts();
     }
     return data;
   } catch (err) {
     console.error("contract call failure", err);
   }

```

Similar to the functions we implemented earlier, we call the deletePost function and passing the post id to it, and pass it down to Post component

```
{posts.map(post =>
         <Post
           ...
           deletePost={deletePost}
         />)
       }
```

Update `Post.js`

```
export default function Post({ ... id, owner, address, deletePost }) {
 return (
   <div className="lg:w-1/2 flex flex-col w-full h-full bg-white overflow-hidden mb-6 shadow-md rounded-[8px] pt-2 pb-5 px-6 box-border">
               { owner === address &&
           <button className="w-[10px] cursor-pointer" onClick={() => deletePost(id)}>
             <svg className="fill-current w-3 h-3" version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="122.878px" height="122.88px" viewBox="0 0 122.878 122.88" enableBackground="new 0 0 122.878 122.88" xml-space="preserve">
               <g>
                 <path d="M1.426,8.313c-1.901-1.901-1.901-4.984,0-6.886c1.901-1.902,4.984-1.902,6.886,0l53.127,53.127l53.127-53.127 c1.901-1.902,4.984-1.902,6.887,0c1.901,1.901,1.901,4.985,0,6.886L68.324,61.439l53.128,53.128c1.901,1.901,1.901,4.984,0,6.886 c-1.902,1.902-4.985,1.902-6.887,0L61.438,68.326L8.312,121.453c-1.901,1.902-4.984,1.902-6.886,0 c-1.901-1.901-1.901-4.984,0-6.886l53.127-53.128L1.426,8.313L1.426,8.313z"></path>
               </g>
             </svg>
           </button>
         }      


     </div>
 )
}

```

Here we are checking if the owner of the post has the same address that is connected to the app, and if so showing the close button.

One final thing that is left, let's add some conditional styles and text to the submit button. We need to disable it if the app is not connected to the wallet or not connected to the Mumbai network, or if the publishLoading state variable is true.

**index.js**

```
... 
const buttonText = !address ? "Connect Wallet" : chainId !== ChainId.Mumbai ? "Connect to the Mumbai TestNet" : publishLoading ? "Loading..." : "Publish";
 const buttonDisabled = !address || chainId !== ChainId.Mumbai || publishLoading;
...

```
Path both variables to **Form** component

```
<Form
        ...
         buttonDisabled={buttonDisabled}
         buttonText={buttonText}
       />
```

Update `Form.js`

```
export default function Form({ ... buttonText, buttonDisabled }) {
 return (
   <form className="lg:w-1/2 flex flex-col w-full h-full bg-white overflow-hidden mb-[50px] shadow-md rounded-[8px] py-5 px-10 box-border" onSubmit={() => publishPost(text)}>


       ...


       <div className="w-full justify-center items-center">
       <button className="w-full bg-[#1974E7] px-2 py-2 rounded-[6px] flex justify-center cursor-pointer disabled:opacity-50 disabled:cursor-default" type='submit' disabled={buttonDisabled} onClick={() => publishPost(text)}>
           <h2 className='font-openSans text-white transition'>{buttonText}</h2>
       </button>
       </div>


   </form>
 )
}

```

Notice how we are using the Tailwind's `disabled:` modifier to customize the disabled style.

And that's a wrap!

Full code can be found in the github repo [here](https://github.com/thisisgazzar/next3blog).


## Conclusion: 

Whew, you made it to the end of the tutorial! You now have a solid understanding of how to build a full-stack dApp using some of the coolest and trendiest technologies out there. To take your learning further, you can add the **update** functionality to the post content, **loading** state for **getPosts**, and you can also use packages like **Draft.js** to add a customizable text editor instead of the simple input that we implemented. Additionally, you can implement user profile **routing**.

Thanks for following along. Have a good one!