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 salah3. SVG Icons dari Bootstrap Icons
Sumber: https://icons.getbootstrap.com
- Pilih icon → Copy SVG code → Paste di JSX
- Ubah atribut HTML → JSX:
fill→fill(tetap sama)class→classNameviewBox→viewBox(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 Array | Kapan Dijalankan |
|---|---|
[] (kosong) | Hanya sekali saat component mount |
[isCorrect] | Setiap kali isCorrect berubah |
| Tidak ada | Setiap 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
| Hook | Kegunaan 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]