跪拜 Guibai
← Back to the summary

Five AI-Generated Frontend Bugs That Ship to Production and How to Catch Them

Problem Scenarios

"I used Cursor to generate a React component. It ran fine locally, but threw errors in production. After investigating for ages, I found the AI had written the dependency array for useEffect incorrectly, causing an infinite loop." "Copilot helped me write a table component. It looked fine, but then customers reported the table wasn't updating its data…"

If you're using AI to assist with writing frontend code, you've likely encountered similar "AI hallucination bugs." This article compiles five of the most common pitfalls and how to quickly locate and fix them.


Pitfall 1: Missing Dependencies in React useEffect

Symptoms

AI-generated code:

function UserProfile({ userId }: { userId: string }) {
  const [user, setUser] = useState<User | null>(null);

  useEffect(() => {
    fetch(`/api/users/${userId}`)
      .then(res => res.json())
      .then(setUser);
  }, []); // ⚠️ AI missed userId

The page loads correctly the first time, but the data doesn't change when switching users. In React Strict Mode during development, it runs twice, and the problem surfaces in production.

Cause

AI often treats [] as the default way to "run only once," ignoring external dependencies. This is especially common when the prompt only mentions "fetch user data" without specifying "re-fetch when userId changes."

Fix

useEffect(() => {
  fetch(`/api/users/${userId}`)
    .then(res => res.json())
    .then(setUser);
}, [userId]); // ✅ Added userId

Advanced approach: Replace raw useEffect with useData or react-query. AI can also easily generate request logic with caching, which is safer than manually managing dependencies.


Pitfall 2: CSS Class Name Conflicts (Mixing Tailwind and Custom Styles)

Symptoms

AI-generated component:

<div className="flex items-center card-container">

Tailwind's flex items-center works fine, but the CSS Module class name card-container isn't automatically injected—because the AI wrote the CSS Modules styles.cardContainer as a plain string.

Cause

The AI mixed three styling approaches—global CSS, Tailwind, and CSS Modules—in a single component without following the project's conventions. It inferred "this should work" based on various code it has seen, but in a CSS Modules project, className="card-container" won't be transformed into a hashed class name.

Fix

// ✅ Unify using CSS Modules
import styles from './UserProfile.module.css';

<div className={`${styles.cardContainer} flex items-center`}>

General principle:


Pitfall 3: Abuse of TypeScript Type Assertions

Symptoms

When the AI encounters a type error, its favorite trick is as any:

const data = response.data as any; // 🤦 One bad apple spoils the whole bunch
const items = data.items.map((item: any) => ({
  ...item,
  label: item.name ?? '',
}));

The entire block of code becomes unprotected by types. Anyone building on this code later will see IDE hints all turn to any, rendering TypeScript useless.

Cause

The AI's optimization target is "code that runs," not "type safety." When it detects a type error, it prioritizes the cheapest fix—as any is the cheapest, but also the most harmful.

Fix

// ✅ Correct approach: Define interfaces + type guards
interface ApiResponse<T> {
  code: number;
  data: T;
}

interface Item {
  id: string;
  name: string;
}

function isItemArray(data: unknown): data is Item[] {
  return Array.isArray(data) && data.every(
    item => typeof (item as Item).id === 'string'
  );
}

// Usage
const result: ApiResponse<Item[]> = response.data;
if (!isItemArray(result.data)) throw new Error('Data format abnormal');

Prompting tip: Add "Strictly prohibit the use of as any; complete types must be defined" to your prompt.


Pitfall 4: State Updates Depending on Stale Values

Symptoms

An AI-written counter component:

function Counter() {
  const [count, setCount] = useState(0);

  const handleClick = () => {
    for (let i = 0; i < 5; i++) {
      setCount(count + i); // ⚠️ Each time is 0 + i
    }
  };

After clicking, count becomes 4, not the expected 0+1+2+3+4 = 10. This is because React's state updates are asynchronously batched; each setCount in the loop is based on the same count value.

Cause

The AI doesn't understand React's closure trap and batching mechanism for state updates. It writes what intuitively seems like "add i each time," but the actual execution is "five consecutive calls to setCount(0+i)."

Fix

// ✅ Use functional updates
const handleClick = () => {
  for (let i = 0; i < 5; i++) {
    setCount(prev => prev + i);
  }
};

The same applies to useReducer and Vue's ref/reactive—AI often writes code that reads the latest value directly instead of using functional updates.


Pitfall 5: Error Handling — "As long as it doesn't throw, it's a success"

Symptoms

AI-generated API call code:

async function loadData() {
  try {
    const res = await fetch('/api/data');
    const json = await res.json();
    setData(json);
  } catch (e) {
    console.error(e);
  }
}

Looks fine? The pitfall is in the middle: res.json() can throw a JSON parsing error, and network errors can be caught, but HTTP 400/500 errors will not trigger catch and will be treated as success!

Cause

The AI's "cognitive blind spot": HTTP error codes (4xx/5xx) do not cause fetch to throw an exception; only network disconnection does. The AI's training data contains a large number of simple fetch → then → catch patterns but rarely emphasizes the res.ok check.

Fix

async function loadData() {
  try {
    const res = await fetch('/api/data');

    // ✅ Must manually check HTTP status
    if (!res.ok) {
      throw new Error(`HTTP ${res.status}: ${res.statusText}`);
    }

    const json = await res.json();

    // ✅ Then check business status code
    if (json.code !== 0) {
      throw new Error(json.message || 'Business exception');
    }

    setData(json.data);
  } catch (e) {
    // ✅ Give user-friendly feedback, not just log
    toast.error(e instanceof Error ? e.message : 'Request failed, please retry');
  }
}

Key Takeaways Summary

Pitfall AI's Typical Mistake Fix Preventable via Prompt
useEffect Dependencies Missing dependencies or incorrect [] Complete dependencies / Switch to SWR ✅ "Consider all reactive dependencies"
CSS Conflicts Mixing multiple styling approaches Unify conventions, use CSS Modules ✅ "Strictly follow project CSS conventions"
Type Safety Abuse of as any Define interfaces / Type guards ✅ "Strictly prohibit use of any"
State Updates Depending on stale values, closure traps Functional updates / useReducer Requires manual review
Error Handling Only catching, not checking status codes Check res.ok + status codes ✅ "Handle 4xx/5xx"

Core advice:

  1. AI-written code must be reviewed line by line, especially state management and asynchronous logic
  2. When writing prompts, explicitly constrain ("No any", "Check HTTP status codes")
  3. Use ESLint + TypeScript strict mode to automatically catch low-level errors
  4. AI excels at writing boilerplate code, but business logic and edge cases should be handled by you

AI is an accelerator, not a chauffeur. The more critical the moment, the more you need to keep your own hands on the wheel 🚗