Skip to main content

Write to a Contract with writeContractAsync button

This recipe shows how to implement a button that allows users to interact with a smart contract by executing the writeContractAsync function returned by useScaffoldWriteContract. By following this guide, you can create a user interface for writing data to a contract.

Here is the full code, which we will be implementing in the guide below:
components/Greetings.tsx
import { useState } from "react";
import { parseEther } from "viem";
import { useScaffoldWriteContract } from "~~/hooks/scaffold-eth";

export const Greetings = () => {
const [newGreeting, setNewGreeting] = useState("");

const { writeContractAsync, isPending } = useScaffoldWriteContract("YourContract");

const writeContractAsyncWithParams = () =>
writeContractAsync(
{
functionName: "setGreeting",
args: [newGreeting],
value: parseEther("0.01"),
},
{
onBlockConfirmation: txnReceipt => {
console.log("๐Ÿ“ฆ Transaction blockHash", txnReceipt.blockHash);
},
},
);

return (
<>
<input
type="text"
placeholder="Write your greeting"
className="input border border-primary"
onChange={e => setNewGreeting(e.target.value)}
/>
<button
className="btn btn-primary"
onClick={async () => await writeContractAsyncWithParams()}
disabled={isPending}
>
{isPending ? <span className="loading loading-spinner loading-sm"></span> : <>Send</>}
</button>
</>
);
};

Implementationโ€‹

Step 1: Set Up Your Componentโ€‹

Create a new component in the "components" folder. This component will enable users to write data to a smart contract.

components/Greetings.tsx
export const Greetings = () => {
return (
<>
<input type="text" placeholder="Write your greeting" className="input border border-primary" />
<button>Send</button>
</>
);
};

Step 2: Initialize useScaffoldWriteContract hookโ€‹

Initialize the useScaffoldWriteContract hook to set up the contract interaction. This hook provides the writeContractAsync function for sending transactions, and we'll create writeContractAsyncWithParams to pass the parameters to it.

import { useState } from "react";
import { parseEther } from "viem";
import { useScaffoldWriteContract } from "~~/hooks/scaffold-eth";

export const Greetings = () => {
const { writeContractAsync } = useScaffoldWriteContract("YourContract");

const writeContractAsyncWithParams = () =>
writeContractAsync(
{
functionName: "setGreeting",
args: [newGreeting],
value: parseEther("0.01"),
},
{
onBlockConfirmation: txnReceipt => {
console.log("๐Ÿ“ฆ Transaction blockHash", txnReceipt.blockHash);
},
},
);
return (
<>
<input type="text" placeholder="Write your greeting" className="input border border-primary" />
<button>Send</button>
</>
);
};

Step 3: Add input change logic and send transaction when users click the buttonโ€‹

Design the user interface to allow users to input data and trigger the contract interaction. The example below demonstrates a simple form:

import { useState } from "react";
import { parseEther } from "viem";
import { useScaffoldWriteContract } from "~~/hooks/scaffold-eth";

export const Greetings = () => {
const [newGreeting, setNewGreeting] = useState("");

const { writeContractAsync } = useScaffoldWriteContract("YourContract");

const writeContractAsyncWithParams = () =>
writeContractAsync(
{
functionName: "setGreeting",
args: [newGreeting],
value: parseEther("0.01"),
},
{
onBlockConfirmation: txnReceipt => {
console.log("๐Ÿ“ฆ Transaction blockHash", txnReceipt.blockHash);
},
},
);

return (
<>
<input
type="text"
placeholder="Write your greeting"
className="input border border-primary"
onChange={e => setNewGreeting(e.target.value)}
/>
<button
className="btn btn-primary"
onClick={async () => await writeContractAsyncWithParams()}
>
Send
</button>
</>
);
};

Step 4: Bonus adding loading stateโ€‹

We can use isPending returned from useScaffoldWriteContract while the transaction is being mined and also disable the button.

import { useState } from "react";
import { parseEther } from "viem";
import { useScaffoldWriteContract } from "~~/hooks/scaffold-eth";

export const Greetings = () => {
const [newGreeting, setNewGreeting] = useState("");
const { writeContractAsync, isPending } = useScaffoldWriteContract("YourContract");

const writeContractAsyncWithParams = () =>
writeContractAsync(
{
functionName: "setGreeting",
args: [newGreeting],
value: parseEther("0.01"),
},
{
onBlockConfirmation: txnReceipt => {
console.log("๐Ÿ“ฆ Transaction blockHash", txnReceipt.blockHash);
},
},
);

return (
<>
<input
type="text"
placeholder="Write your greeting"
className="input border border-primary"
onChange={e => setNewGreeting(e.target.value)}
/>

<button
className="btn btn-primary"
onClick={async () => await writeContractAsyncWithParams()}
disabled={isPending}
>
{isPending ? <span className="loading loading-spinner loading-sm"></span> : <>Send</>}
</button>
</>
);
};
Hint

You can also create a writeContractAsync button sending args imperatively, here is an example:

<button
className="btn btn-primary"
onClick={async () =>
await writeContractAsync(
{
contractName: "YourContract",
functionName: "setGreeting",
args: [newGreeting],
value: parseEther("0.01"),
},
{
onBlockConfirmation: txnReceipt => {
console.log("๐Ÿ“ฆ Transaction blockHash", txnReceipt.blockHash);
},
},
)
}
disabled={isPending}
>
{isPending ? <span className="loading loading-spinner loading-sm"></span> : <>Send Imperatively</>}
</button>