Skip to content

Step 1: Understanding UX

Time: ~5 minutes | Type: Learning | Concepts: User experience, loading states, feedback

What We're Learning

Before we add polish to your app, we need to understand what makes software feel professional and why every action needs clear feedback.

Before You Code: Ask AI First

💡 Interactive Learning:

Open your AI assistant and ask these questions. Really read the answers!

What is user experience (UX) in software?
Why do loading states matter? What happens without them?
What is user feedback in apps? Can you give examples?
What makes an error message good vs bad?
Why should buttons be disabled during loading?
What are toast notifications and when should I use them?
What could go wrong if I don't provide feedback for user actions?

What you should learn:

  • UX is how users feel when using your app
  • Loading states prevent confusion and double-submissions
  • Every action needs visible feedback
  • Good error messages are friendly, specific, and helpful
  • Disabled buttons prevent accidental double-clicks
  • Toast notifications provide non-blocking feedback
  • Without feedback, users are confused and frustrated

Understanding the Problem

After asking AI, make sure you understand these scenarios:

Scenario 1: No Loading State

User clicks "Create Todo"

[Button stays normal]

User thinks: "Did it work? Should I click again?"

User clicks again (creating duplicate!)

Two todos appear

Problem: User doesn't know the first click is being processed.

Solution: Show spinner, disable button, provide feedback.


Scenario 2: Bad Error Message

User submits form with empty title

Error: "INVALID_INPUT_ERR_400"

User thinks: "What does that mean? What do I fix?"

Problem: Technical jargon doesn't help users.

Solution: "Title is required. Please enter at least 1 character."


Scenario 3: No Success Feedback

User clicks "Delete"

Todo disappears from list

User thinks: "Did it delete or just hide? Did it work?"

Problem: User isn't sure if action succeeded.

Solution: Show toast: "Todo deleted successfully!"

Good UX Principles

1. Immediate Feedback

Every user action should have immediate visual feedback.

Examples:

  • Click button → Button shows spinner
  • Submit form → Button disables, shows "Saving..."
  • Hover button → Background color changes
  • Focus input → Blue outline appears

2. Clear Communication

Tell users what's happening in plain English.

Examples:

  • Not: "Processing..."
  • Better: "Creating your todo..."
  • Not: "Error occurred"
  • Better: "Title is required"

3. Prevent Mistakes

Stop users from doing things they don't mean to do.

Examples:

  • Disable submit button during loading
  • Ask "Are you sure?" before deleting
  • Validate forms before submitting
  • Show character count for limited inputs

4. Handle Errors Gracefully

When things go wrong, help users fix it.

Examples:

  • Show what went wrong
  • Explain how to fix it
  • Keep their data (don't clear form)
  • Allow retry

Examples: Before and After

Example 1: Creating a Todo

❌ Before (no UX):

typescript
async function createTodo() {
  await addDoc(collection(db, 'todos'), { title, userId });
  router.push('/todos');
}

Problems:

  • No loading state
  • No success message
  • No error handling
  • Button stays clickable (can double-submit)

✅ After (good UX):

typescript
async function createTodo() {
  setLoading(true);  // Show spinner
  try {
    await addDoc(collection(db, 'todos'), { title, userId });
    showToast('Todo created!', 'success');  // Success feedback
    router.push('/todos');
  } catch (error) {
    console.error(error);
    showToast('Could not create todo. Please try again.', 'error');  // Friendly error
  } finally {
    setLoading(false);  // Hide spinner
  }
}

Improvements:

  • Shows loading state
  • Success toast notification
  • Friendly error message
  • Button disabled during loading

Example 2: Delete Button

❌ Before (no confirmation):

tsx
<button onClick={() => deleteTodo(todo.id)}>
  Delete
</button>

Problem: Accidental deletes, no confirmation

✅ After (with confirmation):

tsx
<button onClick={() => {
  if (confirm('Delete this todo?')) {
    deleteTodo(todo.id);
  }
}}>
  Delete
</button>

Improvement: Asks before deleting

Understanding Check

Before moving on, make sure you can answer these:

💡 Ask yourself (or ask AI if unsure):

  1. What happens if a button doesn't show a loading state? (Users might click multiple times)
  2. Why should error messages be in plain English? (Technical jargon confuses users)
  3. When should I show a success message? (After every successful action)
  4. What's wrong with the error "ERR_500"? (Not user-friendly, no context, no solution)
  5. Why disable buttons during loading? (Prevent double-submissions)
  6. What are toast notifications? (Small, temporary messages that auto-dismiss)

Expected answers:

  1. Users confused, may submit multiple times
  2. Users need to understand what went wrong and how to fix it
  3. After create, update, delete, or any action
  4. It's technical, doesn't explain problem or solution
  5. Prevents accidental duplicate submissions
  6. Non-blocking, temporary feedback messages

What You Learned

At this point you should understand:

  • ✅ What user experience (UX) means
  • ✅ Why loading states prevent confusion
  • ✅ Why every action needs feedback
  • ✅ What makes error messages good vs bad
  • ✅ Why buttons should disable during loading
  • ✅ What toast notifications are
  • ✅ The difference between polished and unpolished apps

Next Step

Now that you understand UX principles, let's create a Spinner component to show loading states:

Step 2: Create Spinner →

Built for learning | Open source on GitHub