Skip to content

Interaksi User & Detail Animasi

Gambaran Umum

Video ini membahas cara menangani klik pengguna pada jawaban quiz, menggunakan useState dan useEffect hooks, menampilkan pesan benar/salah dengan animasi, dan menambahkan perhatian terhadap detail seperti SVG ikon dan delayed states.


1. Handling Klik Jawaban

useState Hook

jsx
import React, { useState, useEffect } from "react"

function Quiz(props) {
  const [isCorrect, setIsCorrect] = useState(undefined)
  const [isCorrectDelayed, setIsCorrectDelayed] = useState(undefined)

  function handleAnswer(index) {
    if (index == props.correctAnswer) {
      setIsCorrect(true)
      setIsCorrectDelayed(true)
    } else {
      setIsCorrect(false)
      setIsCorrectDelayed(false)
    }
  }

  return (
    <div className="paying-attention-frontend">
      <p>{props.question}</p>
      <ul>
        {props.answers.map(function(answer, index) {
          return (
            <li 
              onClick={isCorrect === true ? undefined : () => handleAnswer(index)}
              className={
                (isCorrectDelayed === true && index == props.correctAnswer ? "no-click" : "") +
                (isCorrectDelayed === false && index != props.correctAnswer ? " fade-incorrect" : "")
              }
            >
              {answer}
              {isCorrectDelayed === true && index == props.correctAnswer && (
                <svg /* green checkmark SVG */ />
              )}
              {isCorrectDelayed === false && index == props.correctAnswer && (
                <svg /* show correct answer indicator */ />
              )}
            </li>
          );
        })}
      </ul>
    </div>
  );
}

Perbedaan == vs ===

jsx
// correctAnswer disimpan sebagai string "2" (dari TextControl)
// index dari map() adalah number 2

index == props.correctAnswer   // true (loose comparison)
index === props.correctAnswer  // false (strict comparison)

Gunakan == untuk perbandingan ini karena tipe data bisa berbeda (string vs number).


2. Pesan Benar dan Salah

Conditional JSX dengan && Operator

jsx
return (
  <div className="paying-attention-frontend" style={{backgroundColor: props.bgColor}}>
    <p>{props.question}</p>
    <ul>
      {props.answers.map(/* ... */)}
    </ul>

    {/* Pesan Benar */}
    <div className={"correct-message" + (isCorrect === true ? " correct-message--visible" : "")}>
      <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 16 16">
        {/* Smiley face icon dari Bootstrap Icons */}
        <path d="M8 16A8 8 0 1 0 8 0a8 8 0 0 0 0 16zM7 6.5C7 7.328 6.552 8 6 8s-1-.672-1-1.5S5.448 5 6 5s1 .672 1 1.5zM4.285 9.567a.5.5 0 0 1 .683.183A3.498 3.498 0 0 0 8 11.5a3.498 3.498 0 0 0 3.032-1.75.5.5 0 1 1 .866.5A4.498 4.498 0 0 1 8 12.5a4.498 4.498 0 0 1-3.898-2.25.5.5 0 0 1 .183-.683zM10 8c-.552 0-1-.672-1-1.5S9.448 5 10 5s1 .672 1 1.5S10.552 8 10 8z"/>
      </svg>
      <p>That is correct!</p>
    </div>

    {/* Pesan Salah */}
    <div className={"correct-message" + (isCorrect === false ? " correct-message--visible" : "")}>
      <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 16 16">
        {/* Frown face icon dari Bootstrap Icons */}
        <path d="M8 16A8 8 0 1 0 8 0a8 8 0 0 0 0 16zM7 6.5C7 7.328 6.552 8 6 8s-1-.672-1-1.5S5.448 5 6 5s1 .672 1 1.5zm-2.715 5.933a.5.5 0 0 1-.183-.683A4.498 4.498 0 0 1 8 9.5a4.498 4.498 0 0 1 3.898 2.25.5.5 0 0 1-.866.5A3.498 3.498 0 0 0 8 10.5a3.498 3.498 0 0 0-3.032 1.75.5.5 0 0 1-.683.183zM10 8c-.552 0-1-.672-1-1.5S9.448 5 10 5s1 .672 1 1.5S10.552 8 10 8z"/>
      </svg>
      <p>Sorry, try again.</p>
    </div>
  </div>
)

Penting: === true dan === false

jsx
// State awal: isCorrect = undefined

isCorrect === true    // false (undefined !== true)
isCorrect === false   // false (undefined !== false)
isCorrect == false    // true ❌ (undefined loosely equals false!) 

// Gunakan triple equals (===) untuk membedakan 3 state:
// undefined = belum menjawab
// true = jawaban benar
// false = jawaban salah

3. SVG Icons dari Bootstrap Icons

Sumber: https://icons.getbootstrap.com

  • Pilih icon → Copy SVG code → Paste di JSX
  • Ubah atribut HTML → JSX:
    • fillfill (tetap sama)
    • classclassName
    • viewBoxviewBox (tetap, camelCase natural)
    • width, height → tetap sama
jsx
// Contoh: Checkmark icon untuk jawaban benar
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" className="bi bi-check" fill="currentColor" viewBox="0 0 16 16">
  <path d="M10.97 4.97a.75.75 0 0 1 1.07 1.05l-3.99 4.99a.75.75 0 0 1-1.08.02L4.324 8.384a.75.75 0 1 1 1.06-1.06l2.094 2.093 3.473-4.425a.267.267 0 0 1 .02-.022z"/>
</svg>

4. useEffect untuk Reset Jawaban Salah

Auto-Reset Setelah 2.6 Detik

jsx
useEffect(() => {
  if (isCorrect === false) {
    setTimeout(() => {
      setIsCorrect(undefined)
    }, 2600)
  }
}, [isCorrect])

Penjelasan useEffect

jsx
useEffect(() => {
  // Kode di sini dijalankan SETIAP KALI dependency berubah
}, [isCorrect])  // ← dependency array: watch variable ini
Dependency ArrayKapan Dijalankan
[] (kosong)Hanya sekali saat component mount
[isCorrect]Setiap kali isCorrect berubah
Tidak adaSetiap render

5. Staggered Animations dengan isCorrectDelayed

Masalah

Ketika jawaban salah → pesan muncul + jawaban lain fade bersamaan. Brad ingin animasi bertahap (staggered).

Solusi: Dua State

jsx
const [isCorrect, setIsCorrect] = useState(undefined)
const [isCorrectDelayed, setIsCorrectDelayed] = useState(undefined)

handleAnswer

jsx
function handleAnswer(index) {
  if (index == props.correctAnswer) {
    setIsCorrect(true)
    // Delay 500ms untuk styling jawaban list items
    setTimeout(() => {
      setIsCorrectDelayed(true)
    }, 500)
  } else {
    setIsCorrect(false)
    setTimeout(() => {
      setIsCorrectDelayed(false)
    }, 500)
  }
}

useEffect untuk Reset

jsx
useEffect(() => {
  if (isCorrect === false) {
    setTimeout(() => {
      setIsCorrect(undefined)
      setIsCorrectDelayed(undefined)
    }, 2600)
  }
}, [isCorrect])

Timeline Animasi (Jawaban Salah)

0ms      → setIsCorrect(false)        → Pesan "Sorry" muncul
500ms    → setIsCorrectDelayed(false)  → Jawaban salah dikeluarkan, jawaban benar ditampilkan
2600ms   → setIsCorrect(undefined)     → Semua kembali ke awal
           setIsCorrectDelayed(undefined)

Timeline Animasi (Jawaban Benar)

0ms      → setIsCorrect(true)         → Pesan "Correct!" muncul
500ms    → setIsCorrectDelayed(true)  → Jawaban lain di-fade, jawaban benar highlight
         → Tidak ada reset (permanen)

6. Makna CSS Classes

jsx
// Di className setiap <li>:

// Setelah jawaban benar → jawaban yang benar mendapat "no-click"
isCorrectDelayed === true && index == props.correctAnswer ? "no-click" : ""

// Setelah jawaban salah → jawaban yang BUKAN benar di-fade
isCorrectDelayed === false && index != props.correctAnswer ? "fade-incorrect" : ""

Mencegah Klik Setelah Benar

jsx
onClick={isCorrect === true ? undefined : () => handleAnswer(index)}

Setelah menjawab benar, onClick diset ke undefined — tidak ada fungsi yang dijalankan.


7. Catatan React Transition Group

Brad menyebutkan React Transition Group sebagai library bagus untuk animasi DOM add/remove:

  • Official library untuk animasi di React
  • Berguna ketika element ditambah/dihapus dari DOM
  • Dalam kasus quiz ini, Brad menggunakan CSS transitions sebagai gantinya (lebih sederhana)
  • Library tersedia di: react-transition-group

Ringkasan Hooks Yang Digunakan

HookKegunaan dalam Quiz
useState(undefined)Track status jawaban (true/false/undefined)
useEffect([isCorrect])Auto-reset setelah jawaban salah
setTimeout()Delay untuk staggered animation

State Flow

[undefined] → Klik jawaban →  [true]  → Permanen (no reset)
                            →  [false] → 2.6s → [undefined]