Common Pitfalls (Before & After) Flashcards

High-value examples of common pitfalls (9 cards)

1
Q

The “Before” (AI) code snippet to Audit/Fix:

useEffect(() => {
  fetch(`/api/user/${id}`)
  .then(res => res.json())
  .then(data => setUser(data));
}, [id]);
A

The “Dangling Fetch” (Race Condition)

  • Pitfall Label: Missing fetch cleanup / Race Condition.
  • Issue: AI often writes a simple useEffect fetch that doesn’t account for component unmounting.
  • Risk: “Warning: Can’t perform a React state update on an unmounted component”. This leads to memory leaks and UI bugs.
  • The code fix:
useEffect(() => {
  const controller = new AbortController();
  const fetchData = async () => {
    try {
      const res = await fetch(`/api/user/${id}`, { 
        signal: controller.signal });
      const data = await res.json();
      setUser(data);
    } catch (err) {
      if (err.name !== 'AbortError') { 
        console.error(err);
      }
    }
  };
  fetchData();
  
  return () => controller.abort(); // Cleanup
}, [id]);
  • The Technical Justification: > “The implementation fails to account for the asynchronous nature of network requests within the React lifecycle. Without an AbortController or a cleanup flag, a request that resolves after a component has unmounted will attempt to call setUser on a non-existent component. This leads to memory leaks and ‘stale state’ bugs where data from a previous ID might overwrite data for a new ID if the requests resolve out of order.”
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
2
Q

The “Before” (AI) code snippet to Audit/Fix:

function UserProfile({ user }: { user: any }) {
  return <div>{user.profile.name}</div>; 
}
A

The “Lazy Type” (TypeScript Safety)

  • Pitfall Label: Weak API Type Definitions.
  • Issue: Using any or loose interfaces for API responses.
  • Risk: Runtime errors when the API changes or returns unexpected null values.
  • The Fix: Guarding & Defensive Programming, and Optional chaining safety
    ~~~
    interface User {
    id: string;
    profile?: { // Optional chaining safety
    name: string;
    };
    }

const UserProfile: React.FC<{ user: User }> = ({ user }) => {
// Guarding / Defensive Programming
if (!user.profile) return <p>No profile data available.</p>;

return <div>{user.profile.name}</div>;
}
~~~

  • The Technical Justification: “Using any creates a ‘type black hole’ that bypasses the compiler’s primary benefit: static safety. By not defining a proper interface and failing to guard against undefined nested properties (e.g., user.profile), the code is highly brittle. A senior implementation uses optional chaining and explicit interfaces to force the developer to handle the ‘loading’ or ‘missing data’ states at compile-time rather than crashing at runtime.”
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
3
Q

The “Before” (AI) code snippet to Audit/Fix:

useEffect(() => {
  const id = setInterval(() => {
    setCount(count + 1);
  }, 1000);
  return () => clearInterval(id);
}, []);
A

Stale State Closures

  • Pitfall Label: Stale Closure in setInterval.
  • Issue: useEffect capturing the initial value of a variable rather than the current one.
  • Risk: The counter or state will only ever update once or behave inconsistently.
  • The Fix: use a functional update
useEffect(() => {
  const id = setInterval(() => {
    setCount(prev => prev + 1); // Functional update avoids closure issue
  }, 1000);
  return () => clearInterval(id);
}, []);
  • The Technical Justification: (This is what you’d write for the AI company) — “The AI used a direct state reference inside a closure; a functional update is required to ensure the component reflects the most recent state without re-triggering the effect unnecessarily.”
  • More Technical Justification: “The code relies on a stale closure. Because count is captured from the initial render’s scope when the setInterval is created, the function inside the interval will always see the value as 0. Functional updates (e.g., prev => prev + 1) are mandatory here because they ensure React provides the absolute latest state value from the internal state queue, regardless of when the closure was created.”
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
4
Q

The “Before” (AI) code snippet to Audit/Fix:

const data = await response.json();
const user = data as User;
console.log(user.profile.id);
A

The “Blind Cast” (Type Safety)

  • Pitfall Label: Over-reliance on Type Casting (as).
  • Issue: AI often uses as to force a type when it doesn’t know how to handle a complex object.
  • Risk: This is a “silent killer.” If the data structure changes, TS won’t warn you, and the app will crash with Cannot read property of undefined.
  • The (“After”) Fix: Instead of blindly assuming data matches User interface, Type Guard for runtime safety.
const data: unknown = await response.json();

// Type Guard for runtime safety
function isUser(data: any): data is User {
  return data && typeof data.id === 'string' && 'profile' in data;
}

if (isUser(data)) {
  console.log(data.profile.id);
} else {
  throw new Error("Invalid API Response: Data does not match User schema");
}
  • The Technical Justification: “Type assertions (as User) are an ‘escape hatch’ that tell the compiler to stop checking. This is dangerous for external API data, which is unpredictable. The professional approach is to treat incoming data as unknown and use a ‘User-Defined Type Guard’ (a function using is). This migrates the validation from a guess to a runtime guarantee, ensuring the app handles corrupted or unexpected API payloads gracefully.”
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
5
Q

The “Before” (AI) code snippet to Audit/Fix:

function getFirstItem(items: any[]): any {
  return items[0];
}
const item = getFirstItem([1, 2, 3]);
A

The “Any” Generic (Reusability)

  • Pitfall Label: Low-Utility Generics.
  • Issue: Writing “reusable” functions that actually return any.
  • Risk: You lose all the benefits of TypeScript once the data leaves this function.
  • The (“After”) Fix:
function getFirstItem<T>(items: T[]): T | undefined {
  return items[0];
}
const item = getFirstItem([1, 2, 3]); // 'item' is correctly inferred as 'number'
  • The Technical Justification: “The use of any in a utility function is an anti-pattern because it ‘erases’ type information as data flows through the application. By using a Generic (
    <T>
    ), we allow TypeScript to perform ‘Type Inference.’ This ensures that if we pass a User[] into the function, we get a User out, preserving the full IDE autocomplete and type-checking benefits for the rest of the call stack.”
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
6
Q

The “Before” (AI) code snippet to Audit/Fix:

const [data, setData] = useState<Data | null>(null);

useEffect(() => {
  async function loadData() {
    const response = await fetch('/api/data');
    const result = await response.json();
    setData(result); // Risk: If user leaves page, this crashes/leaks
  }
  loadData();
}, []);
A

The “Ghost” State Update (Async Memory Leak)

  • Pitfall Label: Missing isMounted or AbortController in Async Logic.
  • Issue: AI often writes async logic that updates state without checking if the component is still mounted.
  • Risk: While modern React handles some of this better, in many enterprise environments, this causes memory leaks and unpredictable UI flashes when a user navigates away before a slow network request finishes.
setData(result); // Risk: If user leaves page, this crashes/leaks
  • The (“After”) Fix:
useEffect(() => {
  const controller = new AbortController();
  
  (async () => {
    try {
      const response = await fetch('/api/data', { signal: controller.signal });
      const result = await response.json();
      setData(result);
    } catch (err) {
      if (err instanceof Error && err.name === 'AbortError') {
        // Silent catch for intentional cancellation
        return;
      }
      console.error("Fetch failed", err);
    }
  })();

  return () => controller.abort(); // Correctly cancels the request
}, []);
  • The Technical Justification: “Modern React (v18+) has improved memory management, but updating state on an unmounted component still represents a violation of ‘Separation of Concerns.’ By not implementing a cleanup mechanism like AbortController, the browser continues to process network traffic and execute JavaScript for a UI that no longer exists, wasting CPU cycles and potentially triggering side effects in parent components that rely on this child’s state.”
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
7
Q

The “Before” (AI) code snippet to Audit/Fix:

const savedUser: User = JSON.parse(localStorage.getItem('user') || '{}');
return <div>{savedUser.name.toUpperCase()}</div>;
A

The “Implicit Any” via JSON.parse

  • Pitfall Label: Unsafe JSON Parsing.
  • Issue: AI usually assumes JSON.parse returns exactly what you want.
  • Risk: JSON.parse returns any. This bypasses all of TypeScript’s safety, allowing a corrupted localStorage item to crash your entire application at runtime.
const savedUser: User = JSON.parse(localStorage.getItem('user') || '{}');
return <div>{savedUser.name.toUpperCase()}</div>; // Error if name is missing```

- The ("After") Fix: using the "Satisfies" or Guard pattern.

const rawData = localStorage.getItem(‘user’);
const parsedData: unknown = rawData ? JSON.parse(rawData) : null;

// Use a Type Guard to verify the shape before acting
function isValidUser(data: any): data is User {
return data && typeof data.name === ‘string’;
}

if (isValidUser(parsedData)) {
return <div>{parsedData.name.toUpperCase()}</div>;
}

return <div>Please log in.</div>;
~~~

  • The Technical Justification: “Since JSON.parse returns any, it effectively turns off TypeScript for that variable. In a production environment, localStorage can be cleared, corrupted, or manually edited by a user. Explicitly typing the result as unknown and verifying its structure before usage is the only way to prevent a TypeError from cascading through the UI when the local cache doesn’t match the expected schema.”
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
8
Q

The “Before” (AI) code snippet to Audit/Fix:

<div className="submit-btn" onClick={handleSubmit}>
  Submit Form
</div>
A

Semantic HTML & Accessibility (A11y)

  • Pitfall Label: Non-Semantic Interactive Elements.
  • Issue: AI often uses div or span for buttons because it’s “cleaner” visually.
  • Risk: This is a major failure for evaluators. Screen readers cannot “see” a div as a button, and keyboard users cannot tab to it.
  • The (“After”) Fix:
<button 
  type="button" 
  className="submit-btn" 
  onClick={handleSubmit}
  aria-label="Submit the registration form"
>
  Submit Form
</button>
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
9
Q

What is the process of steps you should take when approaching an “Evaluator Audit”?

A

Logic Check: Does it actually work (race conditions, stale closures)?

Type Check: Is it safe (any/unknown, generics, narrowing)?

Performance Check: Are there wasted renders (memoization, unstable keys)?

Standards Check: A11y (ARIA), Security (XSS), and Readability.

Refactor & Justify: Write the fix and explain why (using PEEL: Point, Evidence, Explanation, Link).

How well did you know this?
1
Not at all
2
3
4
5
Perfectly