Leadline Architecture Design

Design Patterns Guide

Classic GoF patterns, React patterns, and common anti-patterns for frontend codebases

Design Patterns Guide

Design patterns are typical solutions to common problems in software design. Each pattern is like a blueprint that you can customize to solve a particular design problem in your code. This guide covers both classic design patterns and React-specific patterns that are particularly useful in modern frontend development.

Why Learn Design Patterns?

Design patterns provide a common vocabulary for developers and help solve recurring design problems efficiently.

  • Common Language: Patterns create a shared vocabulary between developers
  • Proven Solutions: Time-tested approaches to common problems
  • Code Quality: Lead to more maintainable and flexible code
  • Problem Recognition: Help identify when and how to apply specific solutions

Classification of Patterns

Design patterns can be categorized into three main groups:

Creational Patterns

Deal with object creation mechanisms, trying to create objects in a manner suitable to the situation.

Structural Patterns

Deal with object composition, creating relationships between objects to form larger structures.

Behavioral Patterns

Focus on communication between objects and the assignment of responsibilities.



React Anti-Patterns

Understanding what NOT to do is just as important as learning good patterns. Here are common React anti-patterns that can lead to maintainability issues, performance problems, and development headaches.

1. God Component

A component that tries to do everything - handling multiple responsibilities, managing complex state, and containing too much logic.

Problems:

  • Hard to test and debug
  • Difficult to maintain and understand
  • Poor reusability
  • Performance issues
// ❌ God Component Anti-pattern
function UserDashboard() {
  const [users, setUsers] = useState([]);
  const [products, setProducts] = useState([]);
  const [orders, setOrders] = useState([]);
  const [analytics, setAnalytics] = useState({});
  const [notifications, setNotifications] = useState([]);
  const [loading, setLoading] = useState(false);
  const [errors, setErrors] = useState({});

  // Hundreds of lines of logic for different concerns...
  const fetchUsers = async () => {
    /* ... */
  };
  const fetchProducts = async () => {
    /* ... */
  };
  const generateAnalytics = () => {
    /* ... */
  };
  const handleUserUpdate = () => {
    /* ... */
  };
  const handleProductCreate = () => {
    /* ... */
  };
  // ... many more functions

  return (
    <div className="dashboard">
      {/* Massive JSX handling multiple features */}
      <div className="users-section">{/* Complex user management UI */}</div>
      <div className="products-section">{/* Complex product management UI */}</div>
      <div className="analytics-section">{/* Complex analytics UI */}</div>
      {/* ... hundreds more lines of JSX */}
    </div>
  );
}

// ✅ Better approach - Split responsibilities
function UserDashboard() {
  return (
    <div className="dashboard">
      <UserManagement />
      <ProductManager />
      <OrderHistory />
      <AnalyticsPanel />
      <NotificationCenter />
    </div>
  );
}

function UserManagement() {
  const [users, setUsers] = useState([]);
  const [loading, setLoading] = useState(false);

  // Focused logic only for user management
  return <div>{/* User-specific UI */}</div>;
}

2. Props Drilling

Passing props through multiple component layers when only the deeply nested component needs them.

Problems:

  • Makes components tightly coupled
  • Hard to refactor
  • Intermediate components receive props they don't use
  • Maintenance nightmare when prop structure changes
// ❌ Props Drilling Anti-pattern
function App() {
  const [user, setUser] = useState(null);
  const [theme, setTheme] = useState("light");
  const [language, setLanguage] = useState("en");

  return (
    <Layout
      user={user}
      setUser={setUser}
      theme={theme}
      setTheme={setTheme}
      language={language}
      setLanguage={setLanguage}
    />
  );
}

function Layout({ user, setUser, theme, setTheme, language, setLanguage }) {
  return (
    <div className={`layout ${theme}`}>
      <Header
        user={user}
        setUser={setUser}
        theme={theme}
        setTheme={setTheme}
        language={language}
        setLanguage={setLanguage}
      />
      <Sidebar user={user} theme={theme} />
    </div>
  );
}

function Header({ user, setUser, theme, setTheme, language, setLanguage }) {
  return (
    <header>
      <Navigation user={user} />
      <UserMenu
        user={user}
        setUser={setUser}
        theme={theme}
        setTheme={setTheme}
        language={language}
        setLanguage={setLanguage}
      />
    </header>
  );
}

function UserMenu({ user, setUser, theme, setTheme, language, setLanguage }) {
  return (
    <div className="user-menu">
      <span>{user?.name}</span>
      <button onClick={() => setTheme(theme === "light" ? "dark" : "light")}>Toggle Theme</button>
      <button onClick={() => setLanguage(language === "en" ? "es" : "en")}>Change Language</button>
      <button onClick={() => setUser(null)}>Logout</button>
    </div>
  );
}

// ✅ Better approach - Context API
const AppContext = createContext();

function App() {
  const [user, setUser] = useState(null);
  const [theme, setTheme] = useState("light");
  const [language, setLanguage] = useState("en");

  return (
    <AppContext.Provider
      value={{
        user,
        setUser,
        theme,
        setTheme,
        language,
        setLanguage,
      }}
    >
      <Layout />
    </AppContext.Provider>
  );
}

function Layout() {
  const { theme } = useContext(AppContext);
  return (
    <div className={`layout ${theme}`}>
      <Header />
      <Sidebar />
    </div>
  );
}

function UserMenu() {
  const { user, setUser, theme, setTheme, language, setLanguage } = useContext(AppContext);

  return (
    <div className="user-menu">
      <span>{user?.name}</span>
      <button onClick={() => setTheme(theme === "light" ? "dark" : "light")}>Toggle Theme</button>
      <button onClick={() => setLanguage(language === "en" ? "es" : "en")}>Change Language</button>
      <button onClick={() => setUser(null)}>Logout</button>
    </div>
  );
}

3. Premature Optimization

Optimizing code before identifying actual performance bottlenecks, often making code more complex without real benefits.

Problems:

  • Increased complexity
  • Harder to maintain
  • May not solve real performance issues
  • Time wasted on micro-optimizations
// ❌ Premature Optimization Anti-pattern
function UserList({ users }) {
  // Unnecessary useMemo for simple operations
  const sortedUsers = useMemo(() => users.sort((a, b) => a.name.localeCompare(b.name)), [users]);

  // Unnecessary useCallback for simple functions
  const handleClick = useCallback((id) => {
    console.log("User clicked:", id);
  }, []);

  // Over-memoization of simple components
  const MemoizedUserItem = useMemo(
    () =>
      memo(({ user, onClick }) => (
        <div onClick={() => onClick(user.id)}>
          {user.name} - {user.email}
        </div>
      )),
    [],
  );

  return (
    <div>
      {sortedUsers.map((user) => (
        <MemoizedUserItem key={user.id} user={user} onClick={handleClick} />
      ))}
    </div>
  );
}

// ✅ Better approach - Optimize when needed
function UserList({ users }) {
  const handleClick = (id) => console.log("User clicked:", id);

  return (
    <div>
      {users
        .sort((a, b) => a.name.localeCompare(b.name))
        .map((user) => (
          <UserItem key={user.id} user={user} onClick={handleClick} />
        ))}
    </div>
  );
}

// Only optimize if you have performance issues with many items
function OptimizedUserList({ users }) {
  // Use useMemo only for expensive calculations
  const sortedUsers = useMemo(() => users.sort((a, b) => a.name.localeCompare(b.name)), [users]); // Only when users array actually changes

  return (
    <VirtualizedList items={sortedUsers}>
      {({ item: user }) => <UserItem user={user} />}
    </VirtualizedList>
  );
}

4. Global State Coupling

Putting everything in global state or making components too dependent on specific global state structure.

Problems:

  • Hard to test components in isolation
  • Tight coupling between components and state structure
  • Difficult to refactor state shape
  • Performance issues with unnecessary re-renders
// ❌ Global State Coupling Anti-pattern
function UserProfile() {
  const globalState = useGlobalState();

  // Directly accessing deeply nested global state
  const userName = globalState.app.user.profile.personal.name;
  const userEmail = globalState.app.user.profile.contact.email;
  const userAvatar = globalState.app.user.profile.personal.avatar;
  const userPreferences = globalState.app.user.settings.preferences;
  const notifications = globalState.app.user.notifications.unread;

  return (
    <div>
      <img src={userAvatar} alt={userName} />
      <h1>{userName}</h1>
      <p>{userEmail}</p>
      <div>Notifications: {notifications.length}</div>
      <div>Theme: {userPreferences.theme}</div>
    </div>
  );
}

// ✅ Better approach - Selector pattern and local state
const useUserProfile = () => {
  const globalState = useGlobalState();

  return useMemo(
    () => ({
      name: globalState.app.user.profile.personal.name,
      email: globalState.app.user.profile.contact.email,
      avatar: globalState.app.user.profile.personal.avatar,
    }),
    [globalState.app.user.profile],
  );
};

const useUserPreferences = () => {
  const globalState = useGlobalState();
  return globalState.app.user.settings.preferences;
};

const useNotifications = () => {
  const globalState = useGlobalState();
  return globalState.app.user.notifications.unread;
};

function UserProfile() {
  const { name, email, avatar } = useUserProfile();
  const preferences = useUserPreferences();
  const notifications = useNotifications();

  return (
    <div>
      <img src={avatar} alt={name} />
      <h1>{name}</h1>
      <p>{email}</p>
      <div>Notifications: {notifications.length}</div>
      <div>Theme: {preferences.theme}</div>
    </div>
  );
}

5. Components Folder (Poor Organization)

Dumping all components into a single flat folder without proper organization or structure.

Problems:

  • Hard to find specific components
  • No logical grouping
  • Scaling issues as project grows
  • Unclear component relationships
# ❌ Components Folder Anti-pattern
src/
  components/
    Button.jsx
    Header.jsx
    Footer.jsx
    UserCard.jsx
    ProductCard.jsx
    OrderItem.jsx
    ShippingForm.jsx
    PaymentForm.jsx
    LoginModal.jsx
    SignupModal.jsx
    ProductList.jsx
    UserList.jsx
    OrderHistory.jsx
    NavBar.jsx
    SideBar.jsx
    LoadingSpinner.jsx
    ErrorMessage.jsx
    SuccessMessage.jsx
    FormInput.jsx
    FormSelect.jsx
    ... (50+ more components)

# ✅ Better approach - Organized structure
src/
  components/
    ui/                     # Reusable UI components
      Button/
        Button.jsx
        Button.module.css
        Button.stories.js
      Input/
        Input.jsx
        Input.module.css
      Modal/
        Modal.jsx
        Modal.module.css
      Spinner/
        LoadingSpinner.jsx
    layout/                 # Layout components
      Header/
        Header.jsx
        Navigation.jsx
      Footer/
        Footer.jsx
      Sidebar/
        Sidebar.jsx
    features/               # Feature-specific components
      auth/
        LoginModal.jsx
        SignupModal.jsx
        AuthForm.jsx
      user/
        UserCard.jsx
        UserList.jsx
        UserProfile.jsx
      product/
        ProductCard.jsx
        ProductList.jsx
        ProductDetail.jsx
      order/
        OrderItem.jsx
        OrderHistory.jsx
        OrderSummary.jsx
    forms/                  # Form components
      ShippingForm.jsx
      PaymentForm.jsx
      ContactForm.jsx

6. useEffect Driven Development

Using useEffect for everything instead of understanding when it's actually needed, leading to over-complicated component logic.

Problems:

  • Unnecessary re-renders
  • Complex dependency arrays
  • Hard to debug effect chains
  • Performance issues
  • Race conditions
// ❌ useEffect Driven Development Anti-pattern
function UserProfile({ userId }) {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(null);
  const [formattedName, setFormattedName] = useState("");
  const [displayAge, setDisplayAge] = useState("");
  const [isAdult, setIsAdult] = useState(false);

  // Unnecessary effect for simple computation
  useEffect(() => {
    if (user && user.firstName && user.lastName) {
      setFormattedName(`${user.firstName} ${user.lastName}`);
    }
  }, [user]);

  // Another unnecessary effect for simple computation
  useEffect(() => {
    if (user && user.birthDate) {
      const age = calculateAge(user.birthDate);
      setDisplayAge(`${age} years old`);
      setIsAdult(age >= 18);
    }
  }, [user]);

  // Overly complex effect chain
  useEffect(() => {
    setLoading(true);
    setError(null);
  }, [userId]);

  useEffect(() => {
    if (loading && !error) {
      fetchUser(userId)
        .then((userData) => {
          setUser(userData);
          setLoading(false);
        })
        .catch((err) => {
          setError(err.message);
          setLoading(false);
        });
    }
  }, [loading, error, userId]);

  // Yet another effect for analytics
  useEffect(() => {
    if (user && !loading) {
      trackUserView(user.id);
    }
  }, [user, loading]);

  return (
    <div>
      {loading && <div>Loading...</div>}
      {error && <div>Error: {error}</div>}
      {user && (
        <div>
          <h1>{formattedName}</h1>
          <p>{displayAge}</p>
          {isAdult && <p>Adult User</p>}
        </div>
      )}
    </div>
  );
}

// ✅ Better approach - Simpler logic with computed values
function UserProfile({ userId }) {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(null);

  // Single effect for data fetching
  useEffect(() => {
    let cancelled = false;

    setLoading(true);
    setError(null);

    fetchUser(userId)
      .then((userData) => {
        if (!cancelled) {
          setUser(userData);
          trackUserView(userData.id); // Side effect in same place
        }
      })
      .catch((err) => {
        if (!cancelled) {
          setError(err.message);
        }
      })
      .finally(() => {
        if (!cancelled) {
          setLoading(false);
        }
      });

    return () => {
      cancelled = true;
    };
  }, [userId]);

  // Computed values - no effects needed
  const formattedName = user ? `${user.firstName} ${user.lastName}` : "";
  const age = user ? calculateAge(user.birthDate) : 0;
  const displayAge = user ? `${age} years old` : "";
  const isAdult = age >= 18;

  if (loading) return <div>Loading...</div>;
  if (error) return <div>Error: {error}</div>;
  if (!user) return null;

  return (
    <div>
      <h1>{formattedName}</h1>
      <p>{displayAge}</p>
      {isAdult && <p>Adult User</p>}
    </div>
  );
}

7. Low Level Code (Reimplementing existing solutions)

Writing complex low-level implementations instead of using existing libraries or React features.

Problems:

  • Reinventing the wheel
  • More bugs and edge cases
  • Maintenance overhead
  • Missing optimizations
  • Security vulnerabilities
// ❌ Low Level Code Anti-pattern
function CustomDatePicker({ value, onChange }) {
  const [isOpen, setIsOpen] = useState(false);
  const [currentMonth, setCurrentMonth] = useState(new Date().getMonth());
  const [currentYear, setCurrentYear] = useState(new Date().getFullYear());
  const [selectedDate, setSelectedDate] = useState(value);

  // Reimplementing complex date logic
  const getDaysInMonth = (month, year) => {
    return new Date(year, month + 1, 0).getDate();
  };

  const getFirstDayOfMonth = (month, year) => {
    return new Date(year, month, 1).getDay();
  };

  const handleDateClick = (day) => {
    const newDate = new Date(currentYear, currentMonth, day);
    setSelectedDate(newDate);
    onChange(newDate);
    setIsOpen(false);
  };

  const renderCalendar = () => {
    const daysInMonth = getDaysInMonth(currentMonth, currentYear);
    const firstDay = getFirstDayOfMonth(currentMonth, currentYear);
    const days = [];

    // Complex calendar rendering logic...
    for (let i = 0; i < firstDay; i++) {
      days.push(<div key={`empty-${i}`} className="empty-day"></div>);
    }

    for (let day = 1; day <= daysInMonth; day++) {
      days.push(
        <div
          key={day}
          className={`day ${selectedDate?.getDate() === day ? "selected" : ""}`}
          onClick={() => handleDateClick(day)}
        >
          {day}
        </div>,
      );
    }

    return days;
  };

  // Manual keyboard navigation, focus management, accessibility...
  const handleKeyDown = (e) => {
    // Hundreds of lines of keyboard navigation logic
  };

  return (
    <div className="custom-date-picker">
      <input
        type="text"
        value={selectedDate ? selectedDate.toLocaleDateString() : ""}
        onClick={() => setIsOpen(!isOpen)}
        onKeyDown={handleKeyDown}
        readOnly
      />
      {isOpen && (
        <div className="calendar-popup">
          <div className="calendar-header">
            <button onClick={() => setCurrentMonth(currentMonth - 1)}>‹</button>
            <span>
              {currentMonth + 1}/{currentYear}
            </span>
            <button onClick={() => setCurrentMonth(currentMonth + 1)}>›</button>
          </div>
          <div className="calendar-grid">{renderCalendar()}</div>
        </div>
      )}
    </div>
  );
}

// ✅ Better approach - Use established libraries
import DatePicker from "react-datepicker";
import "react-datepicker/dist/react-datepicker.css";

function SimpleDatePicker({ value, onChange }) {
  return (
    <DatePicker
      selected={value}
      onChange={onChange}
      dateFormat="MM/dd/yyyy"
      placeholderText="Select a date"
    />
  );
}

// Or with react-hook-form for form integration
import { Controller } from "react-hook-form";

function FormDatePicker({ control, name, rules }) {
  return (
    <Controller
      control={control}
      name={name}
      rules={rules}
      render={({ field }) => (
        <DatePicker selected={field.value} onChange={field.onChange} dateFormat="MM/dd/yyyy" />
      )}
    />
  );
}

8. Heavy Work (Expensive calculations on every render)

Running expensive calculations on every render instead of memoizing them, causing unnecessary performance bottlenecks.

Problems:

  • Expensive calculations run on every render
  • Performance degradation with complex computations
  • Unresponsive UI during heavy calculations
  • Unnecessary CPU usage
// ❌ Heavy Work Anti-pattern
function DataDashboard({ data, filters, sortOrder }) {
  const [selectedItems, setSelectedItems] = useState([]);
  const [viewMode, setViewMode] = useState("grid");

  // Expensive calculation runs on EVERY render
  const processedData = data
    .filter((item) => {
      // Complex filtering logic
      return (
        filters.categories.includes(item.category) &&
        item.price >= filters.minPrice &&
        item.price <= filters.maxPrice &&
        item.name.toLowerCase().includes(filters.search.toLowerCase())
      );
    })
    .map((item) => {
      // Heavy transformation logic
      return {
        ...item,
        score: calculateComplexScore(item), // Expensive function
        recommendations: generateRecommendations(item), // Another expensive function
        analytics: computeAnalytics(item.history), // Very expensive
      };
    })
    .sort((a, b) => {
      // Complex sorting logic
      switch (sortOrder) {
        case "score":
          return b.score - a.score;
        case "price":
          return a.price - b.price;
        case "popularity":
          return calculatePopularity(b) - calculatePopularity(a); // Expensive
        default:
          return a.name.localeCompare(b.name);
      }
    });

  // This expensive calculation runs even when only selectedItems or viewMode changes!
  const totalValue = processedData.reduce((sum, item) => {
    return sum + item.price * item.quantity * item.score; // Complex calculation
  }, 0);

  return (
    <div>
      <div>Total Value: ${totalValue.toFixed(2)}</div>
      <div>
        View Mode:
        <button onClick={() => setViewMode(viewMode === "grid" ? "list" : "grid")}>
          Toggle View
        </button>
      </div>
      <div>
        {processedData.map((item) => (
          <ItemCard
            key={item.id}
            item={item}
            selected={selectedItems.includes(item.id)}
            onSelect={() => {
              setSelectedItems((prev) =>
                prev.includes(item.id) ? prev.filter((id) => id !== item.id) : [...prev, item.id],
              );
            }}
          />
        ))}
      </div>
    </div>
  );
}

// ✅ Better approach - Use useMemo for expensive calculations
function DataDashboard({ data, filters, sortOrder }) {
  const [selectedItems, setSelectedItems] = useState([]);
  const [viewMode, setViewMode] = useState("grid");

  // Memoize expensive data processing - only recalculates when dependencies change
  const processedData = useMemo(() => {
    console.log("Processing data..."); // This should only log when data/filters/sortOrder change

    return data
      .filter((item) => {
        return (
          filters.categories.includes(item.category) &&
          item.price >= filters.minPrice &&
          item.price <= filters.maxPrice &&
          item.name.toLowerCase().includes(filters.search.toLowerCase())
        );
      })
      .map((item) => ({
        ...item,
        score: calculateComplexScore(item),
        recommendations: generateRecommendations(item),
        analytics: computeAnalytics(item.history),
      }))
      .sort((a, b) => {
        switch (sortOrder) {
          case "score":
            return b.score - a.score;
          case "price":
            return a.price - b.price;
          case "popularity":
            return calculatePopularity(b) - calculatePopularity(a);
          default:
            return a.name.localeCompare(b.name);
        }
      });
  }, [data, filters, sortOrder]); // Only recalculates when these change

  // Memoize total value calculation
  const totalValue = useMemo(() => {
    return processedData.reduce((sum, item) => {
      return sum + item.price * item.quantity * item.score;
    }, 0);
  }, [processedData]); // Only recalculates when processedData changes

  // Memoize the selection handler to prevent unnecessary re-renders
  const handleItemSelect = useCallback((itemId) => {
    setSelectedItems((prev) =>
      prev.includes(itemId) ? prev.filter((id) => id !== itemId) : [...prev, itemId],
    );
  }, []);

  return (
    <div>
      <div>Total Value: ${totalValue.toFixed(2)}</div>
      <div>
        View Mode:
        <button onClick={() => setViewMode(viewMode === "grid" ? "list" : "grid")}>
          Toggle View
        </button>
      </div>
      <div className={viewMode}>
        {processedData.map((item) => (
          <ItemCard
            key={item.id}
            item={item}
            selected={selectedItems.includes(item.id)}
            onSelect={handleItemSelect}
          />
        ))}
      </div>
    </div>
  );
}

// Even better: Custom hook for data processing
function useProcessedData(data, filters, sortOrder) {
  return useMemo(() => {
    return data
      .filter((item) => {
        return (
          filters.categories.includes(item.category) &&
          item.price >= filters.minPrice &&
          item.price <= filters.maxPrice &&
          item.name.toLowerCase().includes(filters.search.toLowerCase())
        );
      })
      .map((item) => ({
        ...item,
        score: calculateComplexScore(item),
        recommendations: generateRecommendations(item),
        analytics: computeAnalytics(item.history),
      }))
      .sort((a, b) => {
        switch (sortOrder) {
          case "score":
            return b.score - a.score;
          case "price":
            return a.price - b.price;
          case "popularity":
            return calculatePopularity(b) - calculatePopularity(a);
          default:
            return a.name.localeCompare(b.name);
        }
      });
  }, [data, filters, sortOrder]);
}

9. Props Plowing (Passing excessive or irrelevant props)

Passing too many props to components or passing props that components don't actually need, creating bloated component interfaces.

Problems:

  • Components become harder to understand and maintain
  • Unclear component responsibilities
  • Performance issues with unnecessary re-renders
  • Makes testing more complex
  • Tight coupling between components
  • Spreading props directly onto DOM elements creates invalid HTML attributes and React warnings
// ❌ Props Plowing Anti-pattern
function UserCard({
  user,
  isSelected,
  onSelect,
  theme,
  language,
  currency,
  timezone,
  permissions,
  analytics,
  deviceInfo,
  appVersion,
  debugMode,
  experimentFlags,
  onUserClick,
  onUserHover,
  onUserFocus,
  onAnalyticsEvent,
  formatDate,
  formatCurrency,
  translateText,
  validatePermission,
  logEvent,
  trackClick,
  showTooltip,
  hideTooltip,
  openModal,
  closeModal,
  redirectTo,
  refreshData,
  cacheData,
}) {
  // Component only uses a few of these props
  const handleClick = () => {
    onSelect(user.id);
    trackClick("user_card", user.id);
    logEvent("user_interaction", { userId: user.id });
  };

  return (
    <div className={`user-card ${theme} ${isSelected ? "selected" : ""}`} onClick={handleClick}>
      <img src={user.avatar} alt={user.name} />
      <h3>{user.name}</h3>
      <p>{user.email}</p>
      {debugMode && <span>Debug: {user.id}</span>}
    </div>
  );
}

// ❌ Another Props Plowing example - Kitchen sink component
function ProductDisplay({
  product,
  user,
  cart,
  wishlist,
  reviews,
  recommendations,
  categories,
  brands,
  suppliers,
  inventory,
  pricing,
  discounts,
  shipping,
  returns,
  warranty,
  specifications,
  images,
  videos,
  documents,
  analytics,
  experiments,
  ab_tests,
  feature_flags,
  permissions,
  settings,
  preferences,
  locale,
  currency,
  timezone,
  device,
  browser,
  session,
  history,
  breadcrumbs,
  metadata,
  tracking,
  monitoring,
  logging,
  caching,
  api_client,
  error_handler,
  notification_service,
  modal_service,
  tooltip_service,
  loading_service,
  validation_service,
  formatting_service,
  translation_service,
  theme_service,
  layout_service,
  navigation_service,
  search_service,
  filter_service,
  sort_service,
  pagination_service,
  export_service,
  import_service,
  backup_service,
  sync_service,
  offline_service,
  push_notification_service,
  email_service,
  sms_service,
  social_media_service,
  payment_service,
  subscription_service,
  license_service,
  security_service,
  audit_service,
  compliance_service,
  gdpr_service,
  accessibility_service,
  performance_service,
  seo_service,
  // ... and many more
}) {
  // This component is impossible to understand, test, or maintain
  return (
    <div>
      <h1>{product.name}</h1>
      <p>{product.description}</p>
      {/* Uses only a tiny fraction of the passed props */}
    </div>
  );
}

// ❌ Passing all app state as props
function Header({
  user,
  cart,
  notifications,
  settings,
  theme,
  language,
  currency,
  location,
  history,
  search,
  filters,
  products,
  categories,
  orders,
  wishlist,
  reviews,
  recommendations,
  analytics,
  experiments,
  permissions,
  // ... entire application state
  onLogin,
  onLogout,
  onSearch,
  onFilter,
  onSort,
  onNavigate,
  onThemeChange,
  onLanguageChange,
  onCurrencyChange,
  // ... all possible actions
}) {
  // Header only needs user, cart count, and a few actions
  return (
    <header>
      <Logo />
      <SearchBar onSearch={onSearch} />
      <CartIcon count={cart.items.length} />
      <UserMenu user={user} onLogout={onLogout} />
    </header>
  );
}

// ❌ Spreading props directly onto DOM elements
function BadButton({ onClick, disabled, children, analytics, user, theme, ...props }) {
  // Dangerous: spreading all props onto DOM element
  // This can add invalid HTML attributes and cause React warnings
  return (
    <button
      onClick={onClick}
      disabled={disabled}
      {...props} // ❌ analytics, user, theme become invalid HTML attributes
    >
      {children}
    </button>
  );
}

// ❌ Another example of bad prop spreading
function BadInput({ value, onChange, validation, formatting, analytics, tracking, ...allProps }) {
  return (
    <input
      value={value}
      onChange={onChange}
      {...allProps} // ❌ validation, formatting, analytics, tracking are not valid HTML attributes
    />
  );
}
// ✅ Better approach - Focused component interfaces
function UserCard({ user, isSelected, onSelect, theme }) {
  const { trackClick } = useAnalytics();
  const { logEvent } = useLogger();

  const handleClick = () => {
    onSelect(user.id);
    trackClick('user_card', user.id);
    logEvent('user_interaction', { userId: user.id });
  };

  return (
    <div
      className={`user-card ${theme} ${isSelected ? 'selected' : ''}`}
      onClick={handleClick}
    >
      <img src={user.avatar} alt={user.name} />
      <h3>{user.name}</h3>
      <p>{user.email}</p>
    </div>
  );
}

// ✅ Focused product component with specific props
function ProductDisplay({ product }) {
  const { user } = useAuth();
  const { addToCart } = useCart();
  const { addToWishlist } = useWishlist();
  const { currency } = useLocalization();
  const { trackEvent } = useAnalytics();

  const handleAddToCart = () => {
    addToCart(product);
    trackEvent('add_to_cart', { productId: product.id });
  };

  return (
    <div className="product-display">
      <ProductImages images={product.images} />
      <ProductInfo
        name={product.name}
        description={product.description}
        price={product.price}
        currency={currency}
      />
      <ProductActions
        onAddToCart={handleAddToCart}
        onAddToWishlist={() => addToWishlist(product)}
        inStock={product.inventory > 0}
      />
      {user && <ProductReviews productId={product.id} />}
    </div>
  );
}

// ✅ Clean header with minimal props
function Header() {
  const { user, logout } = useAuth();
  const { cartCount } = useCart();
  const { search, setSearch } = useSearch();

  return (
    <header>
      <Logo />
      <SearchBar value={search} onChange={setSearch} />
      <CartIcon count={cartCount} />
      <UserMenu user={user} onLogout={logout} />
    </header>
  );
}

// ✅ Component composition approach
function UserProfile({ userId }) {
  return (
    <div className="user-profile">
      <UserAvatar userId={userId} />
      <UserInfo userId={userId} />
      <UserStats userId={userId} />
      <UserActions userId={userId} />
    </div>
  );
}

function UserAvatar({ userId }) {
  const { user } = useUser(userId);
  const { theme } = useTheme();

  return (
    <div className={`user-avatar ${theme}`}>
      <img src={user.avatar} alt={user.name} />
    </div>
  );
}

function UserInfo({ userId }) {
  const { user } = useUser(userId);
  const { formatDate } = useFormatting();

  return (
    <div className="user-info">
      <h2>{user.name}</h2>
      <p>{user.email}</p>
      <p>Member since: {formatDate(user.createdAt)}</p>
    </div>
  );
}

// ✅ Props interface definition (TypeScript)
interface UserCardProps {
  user: {
    id: string;
    name: string;
    email: string;
    avatar: string;
  };
  isSelected: boolean;
  onSelect: (userId: string) => void;
  variant?: 'default' | 'compact' | 'detailed';
}

function TypedUserCard({ user, isSelected, onSelect, variant = 'default' }: UserCardProps) {
  // Clear interface with only necessary props
  return (
    <div className={`user-card user-card--${variant}`}>
      {/* Component implementation */}
    </div>
  );
}

// ✅ Using render props to avoid prop plowing
function DataProvider({ children, userId }) {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    fetchUser(userId).then(setUser).catch(setError).finally(() => setLoading(false));
  }, [userId]);

  return children({ user, loading, error, refetch: () => fetchUser(userId) });
}

function UserProfileWithRenderProps({ userId }) {
  return (
    <DataProvider userId={userId}>
      {({ user, loading, error, refetch }) => {
        if (loading) return <LoadingSpinner />;
        if (error) return <ErrorMessage error={error} onRetry={refetch} />;
        if (!user) return <UserNotFound />;

        return (
          <div>
            <UserCard user={user} />
            <UserStats user={user} />
            <UserActions user={user} />
          </div>
        );
      }}
    </DataProvider>
  );
}

// ✅ Proper way to handle DOM props spreading
function GoodButton({
  onClick,
  disabled,
  children,
  variant = 'primary',
  size = 'medium',
  analytics,
  user,
  theme,
  ...domProps // Only spread valid DOM attributes
}) {
  const { trackClick } = useAnalytics();
  const { theme: currentTheme } = useTheme();

  // Filter out only valid DOM attributes
  const validDomProps = {};
  const validAttributes = ['className', 'style', 'id', 'data-testid', 'aria-label', 'title'];

  Object.keys(domProps).forEach(key => {
    if (validAttributes.includes(key) || key.startsWith('data-') || key.startsWith('aria-')) {
      validDomProps[key] = domProps[key];
    }
  });

  const handleClick = (e) => {
    onClick?.(e);
    trackClick('button_click', { variant, user: user?.id });
  };

  return (
    <button
      onClick={handleClick}
      disabled={disabled}
      className={`btn btn--${variant} btn--${size} btn--${currentTheme}`}
      {...validDomProps} // ✅ Only valid DOM attributes
    >
      {children}
    </button>
  );
}

// ✅ Alternative: Explicit prop extraction
function GoodInput({
  value,
  onChange,
  validation,
  formatting,
  analytics,
  tracking,
  // Explicitly destructure valid DOM props
  className,
  placeholder,
  disabled,
  readOnly,
  'data-testid': testId,
  'aria-label': ariaLabel,
  ...restProps
}) {
  const { validateField } = useValidation();
  const { formatValue } = useFormatting();
  const { trackEvent } = useAnalytics();

  const handleChange = (e) => {
    const formatted = formatValue(e.target.value, formatting);
    const isValid = validateField(formatted, validation);

    onChange?.(e, { value: formatted, isValid });
    trackEvent('input_change', { field: testId });
  };

  // Only pass valid HTML attributes to DOM element
  return (
    <input
      value={value}
      onChange={handleChange}
      className={className}
      placeholder={placeholder}
      disabled={disabled}
      readOnly={readOnly}
      data-testid={testId}
      aria-label={ariaLabel}
      // Don't spread restProps - validate them first if needed
    />
  );
}

// ✅ Using a whitelist approach
const VALID_DIV_PROPS = [
  'className', 'style', 'id', 'onClick', 'onMouseEnter', 'onMouseLeave',
  'role', 'tabIndex', 'title'
];

function SafeContainer({ children, analytics, theme, ...props }) {
  const { trackEvent } = useAnalytics();

  // Filter props to only include valid DOM attributes
  const domProps = Object.keys(props)
    .filter(key => VALID_DIV_PROPS.includes(key) || key.startsWith('data-') || key.startsWith('aria-'))
    .reduce((obj, key) => {
      obj[key] = props[key];
      return obj;
    }, {});

  useEffect(() => {
    trackEvent('container_render', { theme });
  }, [trackEvent, theme]);

  return (
    <div {...domProps}>
      {children}
    </div>
  );
}

10. Not Using useCallback When It Would Be Beneficial

Functions declared inside functional components are re-created on every render, which can cause unnecessary re-renders of child components when these functions are passed as props.

Problems:

  • Unnecessary re-renders of child components
  • Performance degradation in complex component trees
  • Breaking React.memo optimizations
  • Inefficient re-creation of stable callback functions
// ❌ Not using useCallback when beneficial
function ParentWithoutCallback() {
  const [count, setCount] = useState(0);
  const [name, setName] = useState("");

  // ❌ This function is recreated on every render
  const handleClick = () => {
    console.log("Button clicked");
    // Some expensive operation
    performExpensiveOperation();
  };

  // ❌ Event handlers recreated on every render
  const handleSubmit = (formData) => {
    console.log("Form submitted:", formData);
    submitToAPI(formData);
  };

  const handleReset = () => {
    setName("");
    setCount(0);
  };

  return (
    <div>
      <h1>Count: {count}</h1>
      <input value={name} onChange={(e) => setName(e.target.value)} placeholder="Enter name" />

      {/* These child components will re-render unnecessarily 
          every time parent re-renders due to name state change */}
      <ExpensiveButton onClick={handleClick} />
      <ExpensiveForm onSubmit={handleSubmit} />
      <ResetButton onClick={handleReset} />
      <CounterDisplay count={count} onIncrement={() => setCount((c) => c + 1)} />
    </div>
  );
}

// ❌ Child component without proper memoization
function ExpensiveButton({ onClick }) {
  console.log("ExpensiveButton rendered"); // This will log on every parent re-render

  return (
    <button onClick={onClick} className="expensive-btn">
      {/* Imagine expensive rendering logic here */}
      {Array.from({ length: 1000 }).map((_, i) => (
        <span key={i} className="expensive-span">

        </span>
      ))}
      Click me
    </button>
  );
}

// ❌ Form component that re-renders unnecessarily
function ExpensiveForm({ onSubmit }) {
  const [formData, setFormData] = useState({ email: "", message: "" });

  console.log("ExpensiveForm rendered"); // Logs on every parent re-render

  const handleSubmit = (e) => {
    e.preventDefault();
    onSubmit(formData);
  };

  return (
    <form onSubmit={handleSubmit}>
      <input
        value={formData.email}
        onChange={(e) => setFormData((prev) => ({ ...prev, email: e.target.value }))}
        placeholder="Email"
      />
      <textarea
        value={formData.message}
        onChange={(e) => setFormData((prev) => ({ ...prev, message: e.target.value }))}
        placeholder="Message"
      />
      <button type="submit">Submit</button>
    </form>
  );
}

// ❌ List component with recreated item handlers
function TodoList({ todos, updateTodo, deleteTodo }) {
  const [filter, setFilter] = useState("all");

  const filteredTodos = todos.filter((todo) => {
    if (filter === "completed") return todo.completed;
    if (filter === "active") return !todo.completed;
    return true;
  });

  return (
    <div>
      <select value={filter} onChange={(e) => setFilter(e.target.value)}>
        <option value="all">All</option>
        <option value="active">Active</option>
        <option value="completed">Completed</option>
      </select>

      {filteredTodos.map((todo) => (
        <TodoItem
          key={todo.id}
          todo={todo}
          // ❌ These functions are recreated on every render
          onUpdate={(id, updates) => updateTodo(id, updates)}
          onDelete={(id) => deleteTodo(id)}
        />
      ))}
    </div>
  );
}
// ✅ Using useCallback when beneficial
function ParentWithCallback() {
  const [count, setCount] = useState(0);
  const [name, setName] = useState("");

  // ✅ Memoized callback - only recreated if dependencies change
  const handleClick = useCallback(() => {
    console.log("Button clicked");
    performExpensiveOperation();
  }, []); // No dependencies, so created only once

  // ✅ Memoized with stable reference
  const handleSubmit = useCallback((formData) => {
    console.log("Form submitted:", formData);
    submitToAPI(formData);
  }, []); // No dependencies needed

  // ✅ Memoized callback that depends on setter functions (which are stable)
  const handleReset = useCallback(() => {
    setName("");
    setCount(0);
  }, []); // setName and setCount are stable, so no deps needed

  // ✅ Callback with dependency
  const handleNamedIncrement = useCallback(() => {
    console.log(`Incrementing for ${name}`);
    setCount((c) => c + 1);
  }, [name]); // Recreated only when name changes

  return (
    <div>
      <h1>Count: {count}</h1>
      <input value={name} onChange={(e) => setName(e.target.value)} placeholder="Enter name" />

      {/* These child components won't re-render unnecessarily */}
      <ExpensiveButton onClick={handleClick} />
      <ExpensiveForm onSubmit={handleSubmit} />
      <ResetButton onClick={handleReset} />
      <CounterDisplay count={count} onIncrement={useCallback(() => setCount((c) => c + 1), [])} />
    </div>
  );
}

// ✅ Properly memoized child component
const ExpensiveButton = React.memo(({ onClick }) => {
  console.log("ExpensiveButton rendered"); // Only logs when onClick reference changes

  return (
    <button onClick={onClick} className="expensive-btn">
      {Array.from({ length: 1000 }).map((_, i) => (
        <span key={i} className="expensive-span">

        </span>
      ))}
      Click me
    </button>
  );
});

// ✅ Memoized form component
const ExpensiveForm = React.memo(({ onSubmit }) => {
  const [formData, setFormData] = useState({ email: "", message: "" });

  console.log("ExpensiveForm rendered"); // Only when onSubmit changes

  const handleSubmit = useCallback(
    (e) => {
      e.preventDefault();
      onSubmit(formData);
    },
    [formData, onSubmit],
  );

  const handleEmailChange = useCallback((e) => {
    setFormData((prev) => ({ ...prev, email: e.target.value }));
  }, []);

  const handleMessageChange = useCallback((e) => {
    setFormData((prev) => ({ ...prev, message: e.target.value }));
  }, []);

  return (
    <form onSubmit={handleSubmit}>
      <input value={formData.email} onChange={handleEmailChange} placeholder="Email" />
      <textarea value={formData.message} onChange={handleMessageChange} placeholder="Message" />
      <button type="submit">Submit</button>
    </form>
  );
});

// ✅ Optimized list with memoized callbacks
function TodoList({ todos, updateTodo, deleteTodo }) {
  const [filter, setFilter] = useState("all");

  // ✅ Memoized filter function
  const filteredTodos = useMemo(() => {
    return todos.filter((todo) => {
      if (filter === "completed") return todo.completed;
      if (filter === "active") return !todo.completed;
      return true;
    });
  }, [todos, filter]);

  // ✅ Memoized handlers
  const handleUpdate = useCallback(
    (id, updates) => {
      updateTodo(id, updates);
    },
    [updateTodo],
  );

  const handleDelete = useCallback(
    (id) => {
      deleteTodo(id);
    },
    [deleteTodo],
  );

  const handleFilterChange = useCallback((e) => {
    setFilter(e.target.value);
  }, []);

  return (
    <div>
      <select value={filter} onChange={handleFilterChange}>
        <option value="all">All</option>
        <option value="active">Active</option>
        <option value="completed">Completed</option>
      </select>

      {filteredTodos.map((todo) => (
        <TodoItem key={todo.id} todo={todo} onUpdate={handleUpdate} onDelete={handleDelete} />
      ))}
    </div>
  );
}

// ✅ Custom hook for stable callbacks
function useStableCallback(callback, deps) {
  const callbackRef = useRef(callback);
  const depsRef = useRef(deps);

  // Update callback if dependencies changed
  if (!depsRef.current || !deps.every((dep, i) => dep === depsRef.current[i])) {
    callbackRef.current = callback;
    depsRef.current = deps;
  }

  return useCallback((...args) => callbackRef.current(...args), []);
}

// ✅ Using the custom hook
function ComponentWithStableCallback({ data, onDataChange }) {
  const stableCallback = useStableCallback(
    (newData) => {
      console.log("Processing data:", newData);
      onDataChange(newData);
    },
    [onDataChange],
  );

  return <ChildComponent onUpdate={stableCallback} />;
}

// ✅ When NOT to use useCallback (avoid premature optimization)
function SimpleComponent() {
  const [count, setCount] = useState(0);

  // ❌ Don't do this - premature optimization
  // const handleClick = useCallback(() => setCount(c => c + 1), []);

  // ✅ This is fine - simple handler without child components
  const handleClick = () => setCount((c) => c + 1);

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={handleClick}>Increment</button>
      {/* No child components receiving this callback */}
    </div>
  );
}

11. Messy Events (Inline arrow functions with parameters)

Using inline arrow functions for event handlers that need additional parameters, creating messy JSX and potential performance issues.

Problems:

  • Creates new function instances on every render
  • Makes JSX hard to read and maintain
  • Can cause unnecessary re-renders of child components
  • Difficult to test event handlers
// ❌ Messy Events Anti-pattern
function ProductList({ products, onProductUpdate, onProductDelete }) {
  const [selectedCategory, setSelectedCategory] = useState("all");
  const [sortOrder, setSortOrder] = useState("asc");

  const handleQuantityChange = (productId, newQuantity, event) => {
    event.preventDefault();
    onProductUpdate(productId, { quantity: newQuantity });
  };

  const handlePriceChange = (productId, newPrice, currency, event) => {
    event.preventDefault();
    onProductUpdate(productId, { price: newPrice, currency });
  };

  const confirmDelete = (productId, productName, event) => {
    event.preventDefault();
    if (window.confirm(`Delete ${productName}?`)) {
      onProductDelete(productId);
    }
  };

  return (
    <div>
      {products.map((product) => (
        <div key={product.id} className="product-item">
          <h3>{product.name}</h3>

          {/* Messy inline arrow functions */}
          <input
            type="number"
            value={product.quantity}
            onChange={(e) => handleQuantityChange(product.id, e.target.value, e)}
          />

          <input
            type="number"
            value={product.price}
            onChange={(e) => handlePriceChange(product.id, e.target.value, product.currency, e)}
          />

          <select
            value={product.category}
            onChange={(e) => onProductUpdate(product.id, { category: e.target.value })}
          >
            <option value="electronics">Electronics</option>
            <option value="clothing">Clothing</option>
            <option value="books">Books</option>
          </select>

          <button onClick={(e) => confirmDelete(product.id, product.name, e)}>Delete</button>

          {/* Even messier with multiple parameters */}
          <button
            onClick={(e) => {
              e.preventDefault();
              onProductUpdate(product.id, {
                featured: !product.featured,
                lastModified: new Date().toISOString(),
              });
            }}
          >
            {product.featured ? "Unfeature" : "Feature"}
          </button>
        </div>
      ))}
    </div>
  );
}

// ✅ Better approach - Curried functions and clean event handlers
function ProductList({ products, onProductUpdate, onProductDelete }) {
  const [selectedCategory, setSelectedCategory] = useState("all");
  const [sortOrder, setSortOrder] = useState("asc");

  // Curried function for quantity changes
  const handleQuantityChange = (productId) => (event) => {
    event.preventDefault();
    onProductUpdate(productId, { quantity: event.target.value });
  };

  // Curried function for price changes
  const handlePriceChange = (productId, currency) => (event) => {
    event.preventDefault();
    onProductUpdate(productId, {
      price: event.target.value,
      currency,
    });
  };

  // Curried function for category changes
  const handleCategoryChange = (productId) => (event) => {
    onProductUpdate(productId, { category: event.target.value });
  };

  // Curried function for delete confirmation
  const handleDelete = (productId, productName) => (event) => {
    event.preventDefault();
    if (window.confirm(`Delete ${productName}?`)) {
      onProductDelete(productId);
    }
  };

  // Curried function for feature toggle
  const handleFeatureToggle = (productId, currentFeatured) => (event) => {
    event.preventDefault();
    onProductUpdate(productId, {
      featured: !currentFeatured,
      lastModified: new Date().toISOString(),
    });
  };

  return (
    <div>
      {products.map((product) => (
        <div key={product.id} className="product-item">
          <h3>{product.name}</h3>

          {/* Clean, readable event handlers */}
          <input
            type="number"
            value={product.quantity}
            onChange={handleQuantityChange(product.id)}
          />

          <input
            type="number"
            value={product.price}
            onChange={handlePriceChange(product.id, product.currency)}
          />

          <select value={product.category} onChange={handleCategoryChange(product.id)}>
            <option value="electronics">Electronics</option>
            <option value="clothing">Clothing</option>
            <option value="books">Books</option>
          </select>

          <button onClick={handleDelete(product.id, product.name)}>Delete</button>

          <button onClick={handleFeatureToggle(product.id, product.featured)}>
            {product.featured ? "Unfeature" : "Feature"}
          </button>
        </div>
      ))}
    </div>
  );
}

// Alternative approach: Using data attributes
function ProductListWithDataAttributes({ products, onProductUpdate, onProductDelete }) {
  // Single event handler that reads data attributes
  const handleInputChange = (event) => {
    const { value, dataset, type } = event.target;
    const { productId, field, currency } = dataset;

    const updateData = { [field]: type === "number" ? Number(value) : value };
    if (currency) updateData.currency = currency;

    onProductUpdate(productId, updateData);
  };

  const handleButtonClick = (event) => {
    const { dataset } = event.target;
    const { action, productId, productName, currentFeatured } = dataset;

    switch (action) {
      case "delete":
        if (window.confirm(`Delete ${productName}?`)) {
          onProductDelete(productId);
        }
        break;
      case "toggle-feature":
        onProductUpdate(productId, {
          featured: currentFeatured !== "true",
          lastModified: new Date().toISOString(),
        });
        break;
    }
  };

  return (
    <div>
      {products.map((product) => (
        <div key={product.id} className="product-item">
          <h3>{product.name}</h3>

          <input
            type="number"
            value={product.quantity}
            data-product-id={product.id}
            data-field="quantity"
            onChange={handleInputChange}
          />

          <input
            type="number"
            value={product.price}
            data-product-id={product.id}
            data-field="price"
            data-currency={product.currency}
            onChange={handleInputChange}
          />

          <select
            value={product.category}
            data-product-id={product.id}
            data-field="category"
            onChange={handleInputChange}
          >
            <option value="electronics">Electronics</option>
            <option value="clothing">Clothing</option>
            <option value="books">Books</option>
          </select>

          <button
            data-action="delete"
            data-product-id={product.id}
            data-product-name={product.name}
            onClick={handleButtonClick}
          >
            Delete
          </button>

          <button
            data-action="toggle-feature"
            data-product-id={product.id}
            data-current-featured={product.featured}
            onClick={handleButtonClick}
          >
            {product.featured ? "Unfeature" : "Feature"}
          </button>
        </div>
      ))}
    </div>
  );
}

How to Avoid React Anti-Patterns

Remember: Code should be readable first, performant second. Optimize only when you have identified actual bottlenecks.

  1. Follow Single Responsibility Principle - Each component should have one clear purpose
  2. Use appropriate state management - Local state for component-specific data, Context/Redux for shared data
  3. Measure before optimizing - Use React DevTools Profiler to identify real performance bottlenecks
  4. Organize code logically - Group related components and use consistent naming conventions
  5. Understand React's built-in optimizations - Don't over-engineer simple solutions
  6. Leverage existing libraries - Don't reinvent common UI patterns (date pickers, forms, etc.)
  7. Keep effects simple and focused - Use useEffect only for side effects, not computations
  8. Use proper TypeScript - Type safety helps catch many anti-patterns early
  9. Write tests - Tests often reveal when components are doing too much
  10. Regular code reviews - Fresh eyes can spot anti-patterns you might miss

Best Practices

Don't overuse patterns! Apply them only when they solve a real problem and make your code more maintainable.

When to Use Patterns

  1. Identify the Problem First: Don't use patterns for the sake of using them
  2. Consider Alternatives: Sometimes a simple solution is better than a pattern
  3. Team Knowledge: Ensure your team understands the patterns you're using
  4. Performance Impact: Some patterns can impact performance, especially in React

Pattern Categories

This document covers several categories of design patterns:

  • 📋 Gang of Four (GoF) Patterns: Classic design patterns adapted for modern React development
  • ⚛️ React-Specific Patterns: Patterns unique to React ecosystem and component architecture
  • ❌ Anti-Patterns: Common mistakes and problematic approaches to avoid in React applications

Each pattern includes detailed explanations, code examples, and practical use cases. The patterns are described in detail in the sections below.

Resources

Conclusion

Design patterns are powerful tools that can help you write better, more maintainable code. However, remember that patterns are solutions to specific problems. Understanding the problem you're trying to solve is more important than knowing all the patterns by heart.

Start with simple solutions and refactor to patterns when complexity grows. Focus on the patterns that are most relevant to your current projects and gradually expand your knowledge as needed.

This page and links

Loading map…

On this page