UTA DevHub
Guides

Common Pitfalls & Solutions

Real-world mistakes developers make and how to fix them - learn from others' experiences

Common Pitfalls & Solutions

Overview

This guide documents the most common mistakes developers make in our codebase and provides clear solutions. Each pitfall includes real examples from code reviews, why it's problematic, and the correct approach.

For Junior Developers: These are mistakes we've all made. Learning from them will accelerate your growth.

For AI Assistants: Avoid these patterns and apply the solutions when generating code.

Pitfall 1: Creating Duplicate Business Logic

The Problem: Implementing the same business logic in multiple places instead of using existing domains.

// features/checkout/utils/calculateDiscount.ts
export function calculateDiscount(price: number, discountPercent: number) {
  return price * (discountPercent / 100);
}
 
// features/product-detail/components/PriceDisplay.tsx
function PriceDisplay({ product }) {
  // Reimplementing discount calculation
  const discount = product.price * (product.discountPercent / 100);
  const finalPrice = product.price - discount;
  
  return <Text>${finalPrice}</Text>;
}

Pitfall 2: Wrong File Placement

The Problem: Putting files in incorrect locations, breaking the project structure.

# Common wrong placements:
 
# 1. Domain logic in features
src/features/products/api/productService.ts
src/features/products/hooks/useProducts.ts
 
# 2. Shared components in features
src/features/home/components/Button.tsx
 
# 3. Feature-specific utils in shared
src/core/shared/utils/calculateCartTotal.ts
 
# 4. Wrong UI component placement
src/ui/ProductCard.tsx  # Missing layer
src/ui/components/Button.tsx  # Wrong structure
 
# 5. Mixing concerns in one file
src/features/auth/AuthScreen.tsx # Contains screen, styles, API calls, and business logic

Pitfall 3: Incorrect Import Paths

The Problem: Using relative imports everywhere or incorrect absolute imports.

// Deep relative imports
import { Button } from '../../../ui/Button';
import { useAuth } from '../../../../core/domains/auth/hooks';
import { formatDate } from '../../../shared/utils/date';
 
// Wrong absolute imports
import { Button } from 'ui/Button'; // Missing @/
import { Button } from '@/ui/Button'; // Missing layer
import { useAuth } from '@core/domains/auth'; // Wrong alias
import { api } from 'src/core/api'; // Using src/
 
// Circular imports
// In features/cart/components/CartItem.tsx
import { CartScreen } from '../screens/CartScreen';

State Management Pitfalls

Pitfall 4: State in Wrong Place

The Problem: Putting state at the wrong level or using wrong state type.

// 1. Global state for local data
// stores/uiStore.ts
interface UIStore {
  currentProductFilters: ProductFilters; // Feature-specific!
  isCartDrawerOpen: boolean;
  selectedProductId: string; // View-specific!
}
 
// 2. Local state for shared data
function ProductCard({ product }) {
  // This discount is used by multiple components!
  const [discount, setDiscount] = useState(0);
  
  useEffect(() => {
    // Fetching in component instead of using domain
    fetch(`/api/products/${product.id}/discount`)
      .then(res => res.json())
      .then(data => setDiscount(data.discount));
  }, [product.id]);
}
 
// 3. Props drilling instead of context
function App() {
  const [user, setUser] = useState(null);
  return <Screen1 user={user} setUser={setUser} />;
}
 
function Screen1({ user, setUser }) {
  return <Screen2 user={user} setUser={setUser} />;
}
// ... 5 levels deep

Pitfall 5: Direct State Mutations

The Problem: Mutating state directly instead of creating new references.

// 1. Array mutations
function TodoList() {
  const [todos, setTodos] = useState([]);
  
  const addTodo = (todo) => {
    todos.push(todo); // ❌ Mutating!
    setTodos(todos); // React won't re-render
  };
  
  const removeTodo = (index) => {
    todos.splice(index, 1); // ❌ Mutating!
    setTodos(todos);
  };
}
 
// 2. Object mutations
function UserProfile() {
  const [user, setUser] = useState({ name: '', email: '' });
  
  const updateName = (name) => {
    user.name = name; // ❌ Mutating!
    setUser(user);
  };
}
 
// 3. Nested mutations
function Settings() {
  const [config, setConfig] = useState({
    theme: { primary: 'blue' },
    notifications: { email: true }
  });
  
  const updateTheme = (color) => {
    config.theme.primary = color; // ❌ Deep mutation!
    setConfig(config);
  };
}

Component Pitfalls

Pitfall 6: Inline Everything

The Problem: Creating functions, objects, and styles inline in render.

function ProductList({ products }) {
  return (
    <FlatList
      data={products}
      // ❌ New function every render
      renderItem={({ item }) => (
        <TouchableOpacity 
          // ❌ New function every render
          onPress={() => navigateToProduct(item.id)}
          // ❌ New object every render
          style={{
            padding: 16,
            backgroundColor: '#fff',
            marginBottom: 8
          }}
        >
          <Text>{item.name}</Text>
        </TouchableOpacity>
      )}
      // ❌ New function every render
      keyExtractor={(item) => item.id}
    />
  );
}
 
function UserProfile() {
  return (
    <View>
      <Avatar
        // ❌ New object every render  
        user={{ name: 'John', avatar: 'url' }}
        // ❌ Complex inline computation
        size={Dimensions.get('window').width * 0.3}
      />
    </View>
  );
}

Pitfall 7: Missing Error Boundaries

The Problem: Not handling component errors, causing white screens.

// No error handling at all
function ProductScreen() {
  const { data: product } = useProduct(productId);
  
  // This will crash if product is null!
  return (
    <View>
      <Text>{product.name}</Text>
      <Text>${product.price}</Text>
      <Image source={{ uri: product.images[0] }} />
    </View>
  );
}
 
// Try-catch in component (doesn't work!)
function DataDisplay() {
  try {
    return <ComplexComponent />;
  } catch (error) {
    // This won't catch React errors!
    return <Text>Error occurred</Text>;
  }
}

API & Async Pitfalls

Pitfall 8: Manual API State Management

The Problem: Managing loading, error, and data states manually instead of using React Query.

function ProductList() {
  const [products, setProducts] = useState([]);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(null);
  
  useEffect(() => {
    let cancelled = false;
    
    const fetchProducts = async () => {
      setLoading(true);
      setError(null);
      
      try {
        const response = await fetch('/api/products');
        const data = await response.json();
        
        if (!cancelled) {
          setProducts(data);
        }
      } catch (err) {
        if (!cancelled) {
          setError(err.message);
        }
      } finally {
        if (!cancelled) {
          setLoading(false);
        }
      }
    };
    
    fetchProducts();
    
    return () => {
      cancelled = true;
    };
  }, []);
  
  // Forgot to handle refetch, caching, background updates...
}

Pitfall 9: Race Conditions

The Problem: Not handling async operations properly, causing race conditions.

// 1. Search without debouncing
function SearchScreen() {
  const [query, setQuery] = useState('');
  const [results, setResults] = useState([]);
  
  // This fires on every keystroke!
  useEffect(() => {
    if (query) {
      searchApi(query).then(setResults);
    }
  }, [query]);
}
 
// 2. Not cancelling previous requests
function DataLoader({ id }) {
  const [data, setData] = useState(null);
  
  useEffect(() => {
    // If id changes quickly, responses arrive out of order!
    fetchData(id).then(setData);
  }, [id]);
}
 
// 3. Multiple state updates
function ComplexOperation() {
  const [step1, setStep1] = useState(false);
  const [step2, setStep2] = useState(false);
  const [step3, setStep3] = useState(false);
  
  const process = async () => {
    const result1 = await doStep1();
    setStep1(true); // Component might unmount!
    
    const result2 = await doStep2(result1);
    setStep2(true); // More potential issues
    
    const result3 = await doStep3(result2);
    setStep3(true);
  };
}

TypeScript Pitfalls

Pitfall 10: Type System Misuse

The Problem: Using any, missing types, or wrong type patterns.

// 1. Using any
function processData(data: any) {
  return data.items.map((item: any) => item.name);
}
 
// 2. Missing return types
function calculateTotal(items) { // No parameter or return types!
  return items.reduce((sum, item) => sum + item.price, 0);
}
 
// 3. Inline type assertions everywhere
function UserList() {
  const users = useUsers() as User[]; // Dangerous!
  const config = getConfig() as any; // Even worse!
}
 
// 4. String types instead of unions
interface Status {
  type: string; // Too permissive
  color: string;
}
 
// 5. Optional everything
interface User {
  id?: string;
  name?: string;
  email?: string;
  // Everything optional makes it hard to use
}

Performance Pitfalls

Pitfall 11: Unnecessary Re-renders

The Problem: Components re-rendering more than necessary.

// 1. Object/Array as dependency
function DataProcessor({ config }) {
  const [result, setResult] = useState(null);
  
  useEffect(() => {
    // This runs every render because config is new object!
    processData(config).then(setResult);
  }, [config]); // Object reference changes
}
 
// 2. Inline component definitions
function ParentComponent() {
  // New component every render!
  const ChildComponent = ({ text }) => <Text>{text}</Text>;
  
  return (
    <View>
      <ChildComponent text="Hello" />
    </View>
  );
}
 
// 3. Not memoizing expensive computations
function ExpensiveList({ items, filters }) {
  // Runs on every render!
  const filtered = items.filter(item => {
    return filters.every(filter => filter.test(item));
  });
  
  const sorted = filtered.sort((a, b) => {
    return complexSortLogic(a, b);
  });
  
  return <FlatList data={sorted} />;
}

Quick Reference Card

Before Writing Code, Ask:

  1. Does this logic already exist?

    • Check domains first
    • Search for similar patterns
    • Reuse instead of rewrite
  2. Where should this code go?

  3. Is this the right state type?

    • Server state → React Query
    • Global state → Zustand
    • Local state → useState
    • Form state → React Hook Form
  4. Am I following the patterns?

  5. Have I handled errors?

    • Add error boundaries
    • Handle null/undefined
    • Provide user feedback

For Team Leads

Code Review Checklist

When reviewing PRs, check for these common issues:

  • Code is in the correct location
  • No duplicate business logic
  • Proper TypeScript types (no any)
  • Error handling is present
  • Performance optimizations applied
  • Following established patterns
  • Tests are included

Onboarding New Developers

  1. Start with this guide
  2. Review the Decision Tree together
  3. Pair program on first features
  4. Do thorough code reviews
  5. Encourage questions