Debuggler LogoDebuggler
All challenges
The Phantom Form ErrorFormsFormik
import { useState, useEffect } from "react";

// Mock API that takes longer for shorter strings to simulate a race condition
async function checkUsername(username) {
  const isTaken = username === "amm" || username === "admin";
  // The delay for "amm" is 600ms, but for "ammar" is only 200ms
  const delay = username.length <= 3 ? 600 : 200;
  
  await new Promise(r => setTimeout(r, delay));
  return { available: !isTaken };
}

export default function App() {
  const [username, setUsername] = useState("");
  const [status, setStatus] = useState(""); // "checking", "available", "taken"

  useEffect(() => {
    const b = document.body;
    b.style.background = "#fafafa";
    b.style.margin = "0";
    return () => { b.style.background = ""; b.style.margin = ""; };
  }, []);

  useEffect(() => {
    if (!username) {
      setStatus("");
      return;
    }

    setStatus("checking");
    
    // BUG: Missing cancellation/active tracking.
    // If "amm" (takes 600ms) is typed, then "ammar" (takes 200ms) is typed right after,
    // "ammar" resolves first (available), then "amm" resolves (taken) and overwrites the state.
    checkUsername(username).then((res) => {
      setStatus(res.available ? "available" : "taken");
    });
    
  }, [username]);

  return (
    <div style={styles.page}>
      <div style={styles.card}>
        <h1 style={styles.h1}>Sign Up</h1>
        <p style={styles.sub}>Choose a unique username to continue.</p>
        <input
          data-testid="username-input"
          value={username}
          onChange={(e) => setUsername(e.target.value)}
          placeholder="Username"
          style={styles.input}
        />
        <div style={styles.statusBox}>
          {status === "checking" && <span data-testid="status-checking" style={styles.checking}>Checking availability...</span>}
          {status === "available" && <span data-testid="status-available" style={styles.available}>✓ Username is available</span>}
          {status === "taken" && <span data-testid="status-taken" style={styles.taken}>✗ Username is already taken</span>}
        </div>
      </div>
    </div>
  );
}

const styles = {
  page: { display: "flex", justifyContent: "center", padding: "48px 20px", fontFamily: "system-ui, sans-serif" },
  card: { background: "#fff", borderRadius: 12, padding: "32px", width: "100%", maxWidth: 360, boxShadow: "0 4px 6px -1px rgba(0,0,0,0.1)" },
  h1: { margin: "0 0 8px", fontSize: 24, fontWeight: 600, color: "#111827" },
  sub: { margin: "0 0 24px", fontSize: 14, color: "#6B7280" },
  input: { width: "100%", boxSizing: "border-box", padding: "12px 16px", fontSize: 16, borderRadius: 8, border: "1px solid #D1D5DB", outline: "none", transition: "border-color 0.2s" },
  statusBox: { marginTop: 12, minHeight: 20, fontSize: 14, fontWeight: 500 },
  checking: { color: "#6B7280" },
  available: { color: "#10B981" },
  taken: { color: "#EF4444" }
};

Type a username quickly. Watch as the older validation overwrites the newer one, displaying false errors. Fix the race condition so the final state is always accurate.

After you fix it

  • Typing 'ammar' quickly should display 'Username is available' (not 'taken').
  • The UI should not flicker back to old validation results.

Click "Run checks" to verify your solution.