Making Your dApp Interactive - Understanding Blockchain Writing

Table of contents
- What You'll Learn Today
- The Big Difference: Reading vs Writing
- The User Experience Challenge
- Optimistic UI: The Magic Trick
- Understanding Transaction States
- Smart Error Handling
- Building on Yesterday's Foundation
- Why This Approach Works
- Advanced Concepts for Tomorrow
- Practice Exercise
- Common Beginner Questions
- Tomorrow's Preview
- Complete Implementation

Today we're crossing a major milestone: transforming your read-only app into one that can actually change data on the blockchain. This is where things get exciting - and a bit more complex!
What You'll Learn Today
Today's journey will help you understand:
Why writing to blockchain is fundamentally different from reading
How to handle the "waiting problem" that makes users frustrated
Simple strategies to make your app feel fast even when blockchain is slow
What happens when things go wrong and how to handle it gracefully
๐ Complete Code: All working examples are in our GitHub repository. This tutorial teaches you the concepts - the repo shows you the implementation!
The Big Difference: Reading vs Writing
Let's start with something you already understand and build from there.
Reading: Like Looking at a Book
When you read from a smart contract, it's like looking at a page in a book:
Instant: You open the page and immediately see the content
Free: Looking at a book doesn't cost anything
Always works: The book is always there, ready to be read
typescript
// Reading is instant and free
const greeting = await getGreeting(); // Takes 100ms, costs nothing
console.log(greeting); // "Hello, World!"
Writing: Like Mailing a Letter
When you write to a smart contract, it's like mailing a letter to update a public record:
Takes time: You write the letter, mail it, wait for processing
Costs money: You need to buy a stamp (pay gas fees)
Can fail: Letter might get lost or rejected
typescript
// Writing takes time and costs money
const result = await updateGreeting("New message"); // Takes 30-120 seconds, costs STX
Why is writing so different?
Think about it: when you want to change something on a public blockchain that thousands of computers need to agree on, those computers need time to:
Receive your request
Verify you're allowed to make the change
Agree with other computers that the change is valid
Update their records permanently
This is why blockchain writing is slower and more expensive than traditional apps - but also why it's more trustworthy and permanent.
The User Experience Challenge
Here's the problem every blockchain developer faces: users hate waiting.
The Traditional Web Experience
typescript
// Traditional app - instant feedback
function updateProfile(name) {
database.update({name: name}); // Instant
showSuccess("Profile updated!"); // Immediate feedback
}
The Blockchain Reality
typescript
// Blockchain app - the painful truth
function updateProfile(name) {
blockchain.update({name: name}); // Starts a 30-second process
// User stares at loading spinner for 30+ seconds
// User thinks app is broken
// User gets frustrated and leaves
}
This is the #1 reason people find dApps frustrating to use.
But there's a solution! It's called "optimistic UI" and it's simpler than it sounds.
Optimistic UI: The Magic Trick
The idea is beautifully simple: assume success until proven wrong.
Real-World Analogy
When you send a text message:
Your phone immediately shows the message as "sent"
It doesn't make you wait to confirm the message reached the other person
Only if something goes wrong does it show "failed to send"
This feels natural and fast, even though the actual sending takes time.
Applying This to Blockchain
typescript
// The optimistic approach
async function updateGreeting(newGreeting) {
// Step 1: Immediately update the UI
setDisplayedGreeting(newGreeting);
setIsOptimistic(true); // Show a subtle "pending" indicator
try {
// Step 2: Actually send to blockchain (this takes time)
const result = await blockchain.updateGreeting(newGreeting);
// Step 3: Remove the "pending" indicator when confirmed
setIsOptimistic(false);
} catch (error) {
// Step 4: If it failed, revert the UI change
setDisplayedGreeting(oldGreeting);
setIsOptimistic(false);
showError("Update failed - please try again");
}
}
What the user experiences:
Clicks "Update"
Immediately sees the new greeting (feels instant!)
Sees a small "confirming..." indicator
Either the indicator disappears (success) or the change reverts (failure)
This simple pattern makes blockchain apps feel as responsive as traditional apps.
Understanding Transaction States
Let's think about all the states a blockchain transaction can be in. This helps us give users appropriate feedback.
The Transaction Journey
Imagine you're sending a letter to update your address with the bank:
Writing the letter (User typing in your app)
Walking to mailbox (User clicks submit, wallet opens)
Deciding to mail it (User signs transaction in wallet)
Letter in mail truck (Transaction submitted to blockchain)
Bank processing letter (Transaction being confirmed)
Address updated (Transaction confirmed)
At any step, things can go wrong:
You change your mind (cancel in wallet)
Letter gets lost (network error)
Bank rejects the letter (smart contract rejects transaction)
Translating to Code Concepts
typescript
type TransactionState =
| 'idle' // Nothing happening
| 'signing' // Wallet is open, user deciding
| 'pending' // Submitted, waiting for confirmation
| 'confirmed' // Successfully completed
| 'failed' // Something went wrong
| 'cancelled'; // User said no
Why This Matters for UX
Each state needs different user feedback:
Signing: "Please check your wallet..."
Pending: "Transaction submitted! This usually takes 30-60 seconds..."
Confirmed: "Success! Your change is now permanent on the blockchain."
Failed: "Transaction failed. Don't worry, no money was charged."
Clear communication at each step keeps users informed and reduces anxiety.
Smart Error Handling
Blockchain apps can fail in ways traditional apps never do. Let's understand the common failures and how to handle them gracefully.
Common Failure Scenarios
1. User Rejects in Wallet
typescript
// What happens: User sees wallet popup, clicks "Cancel"
// How to handle: No big deal, just revert UI changes
if (error.message.includes('User rejected')) {
// Don't show this as an error - user made a choice
revertOptimisticChanges();
return;
}
2. Not Enough STX for Gas
typescript
// What happens: User doesn't have enough STX to pay transaction fees
// How to handle: Clear explanation and helpful suggestion
if (error.message.includes('Insufficient funds')) {
showError('You need more STX to complete this transaction. You can get testnet STX from the faucet.');
return;
}
3. Smart Contract Says No
typescript
// What happens: Contract validates input and rejects it
// How to handle: Explain what the contract didn't like
if (error.message.includes('assertion failed')) {
showError('The smart contract rejected this update. Please check your input and try again.');
return;
}
The Golden Rule of Error Messages
Bad error message: "Transaction failed: ContractCallError(err u101)"
Good error message: "Update failed: Username must be between 1 and 20 characters. Please try again."
Always translate technical errors into human-friendly explanations.
Building on Yesterday's Foundation
Remember our simple greeting app from Day 2? Let's add writing capability to it, step by step.
What We're Adding
Currently, our app can:
Connect to a wallet โ
Read the current greeting โ
Today we're adding:
Update the greeting โ
Handle the waiting period gracefully โ
Show clear feedback for all states โ
Recover from errors properly โ
The New Smart Contract Function
First, let's understand what we're calling on the blockchain:
clarity
;; This function accepts a new greeting and updates it
(define-public (set-greeting (new-greeting (string-ascii 50)))
(begin
;; Check if the greeting is too long
(asserts! (<= (len new-greeting) u50) ERR-TOO-LONG)
;; Update the stored greeting
(var-set greeting new-greeting)
;; Return success
(ok "Greeting updated successfully!")
)
)
What this means:
Anyone can call this function
It accepts a string up to 50 characters
It costs a small amount of STX to call
It permanently updates the blockchain
The Frontend Flow
Here's what happens when a user wants to update the greeting:
User types new greeting in an input field
User clicks "Update" button
App immediately shows new greeting (optimistic UI)
App calls smart contract function
User's wallet pops up asking for signature
User signs transaction (or cancels)
If signed, transaction goes to blockchain (30-60 second wait)
Transaction either succeeds or fails
App updates UI based on final result
Handling Each Step
Step 1-2: Basic UI interaction
typescript
const [greeting, setGreeting] = useState('');
const [newGreeting, setNewGreeting] = useState('');
// User types in input field
<input
value={newGreeting}
onChange={(e) => setNewGreeting(e.target.value)}
placeholder="Enter new greeting..."
/>
// User clicks update
<button onClick={handleUpdate}>Update Greeting</button>
Step 3: Optimistic update
typescript
function handleUpdate() {
// Immediately show the new greeting
const previousGreeting = greeting;
setGreeting(newGreeting);
setIsUpdating(true);
// Then try to actually update it...
}
Step 4-9: Blockchain interaction
typescript
async function updateBlockchain(newGreeting) {
try {
const result = await contractCall({
functionName: 'set-greeting',
functionArgs: [stringAsciiCV(newGreeting)]
});
// Success! Remove the "pending" indicator
setIsUpdating(false);
} catch (error) {
// Failed! Revert the optimistic change
setGreeting(previousGreeting);
setIsUpdating(false);
handleError(error);
}
}
Why This Approach Works
This pattern solves the main UX problems with blockchain apps:
Immediate feedback: Users see changes instantly
Clear communication: Users know what's happening at each step
Graceful failure: Errors don't break the app or confuse users
No data loss: Failed transactions don't leave the app in a weird state
Advanced Concepts for Tomorrow
Today we covered the fundamentals of blockchain writing. Tomorrow we'll explore:
Multiple transaction types (not just updating text)
Complex data structures in smart contracts
User permissions and access control
Real-world application patterns
But for now, master today's concepts. The difference between read-only and interactive blockchain apps is huge, and understanding transaction states and optimistic UI will serve you well in any dApp you build.
Practice Exercise
Before tomorrow, try to think through this scenario:
"A user wants to update their greeting, but their wallet is locked. What should happen at each step? How would you communicate this to the user?"
Think about:
What error messages would be helpful?
Should the optimistic UI update happen before or after wallet interaction?
How would you help the user resolve the locked wallet issue?
Common Beginner Questions
Q: "Why can't blockchain be as fast as regular databases?" A: Because regular databases trust one company to maintain them correctly. Blockchains don't trust anyone - they require thousands of computers to independently verify every change. This verification process takes time but creates unprecedented security and permanence.
Q: "What if the user closes their browser while a transaction is pending?" A: The transaction continues on the blockchain independently. When they return, your app should check for any pending transactions and update the UI accordingly. This is why we store transaction IDs and check their status.
Q: "Is optimistic UI lying to users?" A: No more than showing "Message sent" on a text message before confirming delivery. It's giving users immediate feedback while being honest about the current state (showing "pending" indicators). The key is being transparent and reverting gracefully if something fails.
Tomorrow's Preview
Ready to build something more practical? Tomorrow we're creating a task management dApp where users can:
Create and complete tasks
Earn STX rewards for completing tasks
See other users' tasks and progress
Experience real multi-user blockchain interactions
We'll apply everything learned about transaction management to build something people would actually want to use!
Complete Implementation
Remember, all the working code for today's concepts is available in our GitHub repository. The tutorial teaches you why and how - the repo shows you the complete implementation details.
Next up: [Day 4 - Building a Real dApp: Decentralized Task Management]
This is Day 3 of our 30-day Clarity & Stacks.js tutorial series. We're moving from basic concepts to practical applications that demonstrate the real potential of blockchain development.
Essential Learning:
Blockchain writing vs reading fundamentals
Optimistic UI patterns for great UX
Transaction state management
Error handling best practices
Subscribe to my newsletter
Read articles from Gbolahan Akande directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
