Making Your dApp Interactive - Understanding Blockchain Writing

Gbolahan AkandeGbolahan Akande
9 min read

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:

  1. Receive your request

  2. Verify you're allowed to make the change

  3. Agree with other computers that the change is valid

  4. 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:

  1. Writing the letter (User typing in your app)

  2. Walking to mailbox (User clicks submit, wallet opens)

  3. Deciding to mail it (User signs transaction in wallet)

  4. Letter in mail truck (Transaction submitted to blockchain)

  5. Bank processing letter (Transaction being confirmed)

  6. 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:

  1. User types new greeting in an input field

  2. User clicks "Update" button

  3. App immediately shows new greeting (optimistic UI)

  4. App calls smart contract function

  5. User's wallet pops up asking for signature

  6. User signs transaction (or cancels)

  7. If signed, transaction goes to blockchain (30-60 second wait)

  8. Transaction either succeeds or fails

  9. 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:

  1. Immediate feedback: Users see changes instantly

  2. Clear communication: Users know what's happening at each step

  3. Graceful failure: Errors don't break the app or confuse users

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

0
Subscribe to my newsletter

Read articles from Gbolahan Akande directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Gbolahan Akande
Gbolahan Akande