Startseite / Artikel

Zehn verborgene Fallen bei React-Komponenten, die Apps in der React 19-Ära verlangsamen

Zehn häufige Fehler bei React-Komponenten – von fehlendem semantischem HTML bis hin zum Auslassen der Memoisierung – warum React 19 und der React Compiler sie nicht verbergen, samt Lösungen.

Verlag: REACTAPP.TOP16 Min. Lesezeit3642 Wörter

Quellen: 10 React Component Mistakes That Are Secretly Breaking Your App in 2026

React-Komponenten erscheinen einfach, bis kleine Fehler sich zu Problemen in Bezug auf Barrierefreiheit, langsamerem Rendering sowie schwer erkennbaren Fehlern ansammeln. React 19, der React Compiler und Server Components haben die Art und Weise verändert, wie Anwendungen erstellt werden, wodurch solche Fehler teurer statt günstiger werden. Hier sind zehn Fehler auf Komponentenebene, die unauffällig einer Anwendung schaden, zusammen mit der Lösung für jeden davon.

1. Überall <div> statt semantischen HTMLs verwenden

Der Fehler: Für jedes UI-Element wird einfach ein generisches <div> verwendet. Die Navigation wird zu einem <div>. Ein Button wird zu <div onClick={...}>. Ein Artikel wird in einem <div> eingebettet. Selbst ein Form-Label erhält dieselbe Behandlung.

// ❌ Bad: Div soup
function Navbar() {
  return (
    <div className="navbar">
      <div className="logo" onClick={() => router.push('/')}>Logo</div>
      <div className="nav-links">
        <div className="nav-item" onClick={() => setPage('home')}>Home</div>
        <div className="nav-item" onClick={() => setPage('about')}>About</div>
      </div>
    </div>
  );
}

Warum es Ihre App zerstört:

  • Störung der Barrierefreiheit: Assistive Technologien können keine Navigationsmerkmale, interaktiven Steuerelemente oder Überschriften erkennen. Eine Person, die einen Bildschirmleser verwendet, hört etwas wie „klickbar, Text: Startseite“ anstelle von „Navigierung, Link, Startseite.“
  • Störung der Tastaturnutzung: <div>-Tags sind standardmäßig nicht fokussierbar, sodass Nutzer, die mit der Tastatur navigieren, nicht auf Ihr Navigationsmenü zugreifen können.
  • Schwächere SEO-Ergebnisse: Suchmaschinen verlassen sich auf semantische Markierungen wie <nav>, <main> und <article>, um die Struktur einer Seite zu analysieren. Eine Seite, die ausschließlich aus <div>-Tags besteht, wird von Suchmaschinen als von geringer Qualität eingestuft.
  • Serverkomponenten in React 19: Da SSR und Streaming bedeuten, dass der ursprüngliche HTML-Inhalt das ist, was sowohl Benutzer als auch Bots sehen, bevor die Hydratierung einsetzt, ist die semantische Struktur umso wichtiger.
  • Die Lösung:

    // ✅ Good: Semantic HTML
    function Navbar() {
      return (
        <nav className="navbar" aria-label="Main navigation">
          <a href="/" className="logo">Logo</a>
          <ul className="nav-links">
            <li><a href="/">Home</a></li>
            <li><a href="/about">About</a></li>
          </ul>
        </nav>
      );
    }
    

    Best Practices für 2026: Nutzen Sie die neue use API in React 19 zusammen mit Zugänglichkeitsmetadaten sowie Auditing-Tools wie axe-core oder die WAVE-Browsererweiterung, um Ihre Komponenten zu überprüfen. Verwenden Sie <button> für alle klickbaren Elemente, <nav> für Navigationsblöcke und <section> in Kombination mit aria-labelledby, um Bereiche einer Seite zu kennzeichnen.

    2. Inline-Stile in JSX (Leistungsabfall)

    Der Fehler: Das Definieren von Style-Objekten direkt innerhalb von JSX, was bei jeder Renderung eine völlig neue Objektreferenz erzeugt.

    // ❌ Bad: New object every render
    function Badge({ color, children }) {
      return (
        <span style={{ backgroundColor: color, padding: '4px 8px', borderRadius: '4px' }}>
          {children}
        </span>
      );
    }
    

    Warum das Ihre App stört:

    • Zusätzliche Abgleichsarbeiten: Da bei jeder Darstellung ein neues Stilobjekt erstellt wird, erkennt Reacts Differenzierungslogik eine „veränderte“ Eigenschaft und greift den DOM-Node an, obwohl sich eigentlich nichts geändert hat.
    • Schlechtere Leistung bei CSS-in-JS: Bibliotheken, die inline-Stildeklarationen parsen, tragen diesen Aufwand immer wieder, was das Problem verschlimmert.
    • Schwachstellen des Compilers: Der React 19-Compiler kann Komponenten memorisieren, hat aber keine Möglichkeit, inline-Objektreferenzen eigenständig zu stabilisieren. Sobald er ein neu erstelltes Objekt erkennt, überspringt er die Optimierung dieses Teils.
    • Verpasste Caching-Möglichkeiten: Browser können inline-Stildeklarationen nicht so cachen wie Stylesheets. Eine CSS-Klasse wird nur einmal解析t, während inline-Stile bei jeder Montage erneut解析t werden.

    Die Lösung:

    // ✅ Good: CSS classes with dynamic data attributes
    function Badge({ color, children }) {
      return (
        <span
          className="badge"
          data-color={color}
          style={{ '--badge-color': color }}
        >
          {children}
        </span>
      );
    }
    // CSS
    // .badge { background-color: var(--badge-color); padding: 4px 8px; border-radius: 4px; }
    

    Alternativ funktionieren die Tailwind CSS-Utilities gut, wenn sie sorgfältig mit beliebigen Werten verwendet werden:

    // Acceptable: Tailwind utility classes
    function Badge({ color, children }) {
      return (
        <span className="px-2 py-1 rounded" style={{ backgroundColor: color }}>
          {children}
        </span>
      );
    }
    

    Best Practice für 2026: Verwenden Sie statische Styling-Elemente in CSS-Modulen, Tailwind-Klassen oder einem Ansatz mit CSS-in-JS ohne Laufzeitkosten. Bewahren Sie Inline-Stile für wirklich dynamische Werte auf, wie beispielsweise die Breite eines Fortschrittsbalkens – und nutzen Sie dabei möglichst CSS-Custom-Properties.

    3. Falsche Verwendung der key-Eigenschaft in Listen

    Der Fehler: Verlassen Sie sich auf den Array-Index als Schlüssel, überspringen Sie die key-Eigenschaft ganz oder erzeugen Sie bei jeder Neuaufladung einen zufälligen Schlüssel.

    // ❌ Bad: Index as key
    function TodoList({ todos }) {
      return (
        <ul>
          {todos.map((todo, index) => (
            <li key={index}>{todo.text}</li>
          ))}
        </ul>
      );
    }
    // ❌ Worse: Random key
    function TodoList({ todos }) {
      return (
        <ul>
          {todos.map((todo) => (
            <li key={Math.random()}>{todo.text}</li>
          ))}
        </ul>
      );
    }
    

    Warum das Ihre App zerstört:

    • Index-basierte Schlüssel: Wenn die Listeinträge umsortiert werden, ordnet React denselben Schlüssel derselben Position zu und verwendet letztendlich den falschen DOM-Node für den falschen Eintrag. Dadurch vermischen sich der Eingabezustand, der Fokus sowie der interne Komponentenzustand der verschiedenen Einträge.
    • Zufällig generierte Schlüssel: Da bei jeder Renderung ein neuer Schlüssel erzeugt wird, zerstört und neu erstellt React jedes Mal bei einer Aktualisierung jeden DOM-Node. Dies verschlechtert die Leistung erheblich und löscht den Formzustand in der Liste aus.
    • React 19-Übergänge: Bei koncurrentem Rendering und Übergängen können fehlerhafte Schlüssel sichtbare Risse in der Benutzeroberfläche sowie beschädigte Zustände verursachen, sobald eine Aktualisierung unterbrochen wird.

    Die Lösung:

    // ✅ Good: Stable, unique identifier
    function TodoList({ todos }) {
      return (
        <ul>
          {todos.map((todo) => (
            <li key={todo.id}>{todo.text}</li>
          ))}
        </ul>
      );
    }
    

    Falls kein natürlicher, eindeutiger Identifikator verfügbar ist, sollte dieser bei der Erstellung der Daten erstellt werden und nicht erst zur Darstellung:

    // At data creation, not in the component
    const todos = items.map(item => ({
      ...item,
      id: crypto.randomUUID(), // or your backend's ID
    }));
    

    Best Practice für 2026: Wenn eine Liste von einem Server-Komponenten stammt, stellen Sie sicher, dass der Server selbst stabile Identifikatoren bereitstellt. Vermeiden Sie es, Schlüssel aus der Position im Array abzuleiten, es sei denn, die Liste ist tatsächlich statisch und wächst nur durch das Hinzufügen neuer Elemente.

    4. Direkte Änderung von Props

    Der Fehler: Props so zu behandeln, als wären sie veränderliche lokale Variablen, und sie innerhalb einer Kindkomponente zu ändern.

    // ❌ Bad: Mutating props directly
    function SortButton({ items }) {
      const handleSort = () => {
        items.sort((a, b) => a.price - b.price); // MUTATES the prop!
        onSort(items);
      };
    
      return <button onClick={handleSort}>Sort by Price</button>;
    }
    

    Warum das Ihre App zerstört:

    • Stille Korruption des Elternteils: Der Zustand des Elternteils wird verändert, ohne dass man davon erfährt. Reacts Änderungserkennung basiert auf Referenzvergleichen, wodurch die Mutation völlig unbemerkt bleibt.
    • Fehler durch veraltete Neurenderung: Da items weiterhin auf dasselbe Array im Speicher verweist, wird das Elternteil nie neu gerendert, wodurch die Benutzeroberfläche veraltete Daten anzeigt.
    • Verwirrung beim React Compiler: Der Compiler geht davon aus, dass Props unveränderlich durch den Baum fließen. Die direkte Mutation dieser Props macht seine automatische Memoisierungslogik ungültig.
    • Verlust der Vorhersehbarkeit: Jede andere Komponente, die auf dasselbe Array verweist, sieht plötzlich die mutierte, sortierte Version – ohne Warnung.

    Die Lösung:

    // ✅ Good: Immutable update
    function SortButton({ items, onSort }) {
      const handleSort = () => {
        const sorted = [...items].sort((a, b) => a.price - b.price);
        onSort(sorted);
      };
    
      return <button onClick={handleSort}>Sort by Price</button>;
    }
    

    Best Practice für 2026: Wenden Sie Immer an, wenn verschachtelte Mutationen unvermeidlich erscheinen, und setzen Sie bei Verwendung von TypeScript eine ESLint-Regel wie prefer-readonly-props ein. Nehmen Sie an, dass jede Eigenschaft readonly ist, es sei denn, es wird etwas anderes nachgewiesen.

    5. Zu viele Eigenschaften (anstatt Komposition)

    Der Fehler: Entwicklung umfangreicher „Gott-Komponenten“, die Dutzende oder mehr Eigenschaften entgegennehmen, nur um jede mögliche interne Variation steuern zu können.

    // ❌ Bad: Prop explosion
    function Card({
      title,
      subtitle,
      imageUrl,
      imageAlt,
      showImage,
      imagePosition,
      actions,
      showActions,
      actionAlignment,
      badge,
      badgeColor,
      onBadgeClick,
      footer,
      showFooter,
      footerContent,
      borderRadius,
      shadow,
      padding,
      variant,
    }) {
      return (
        <div className={`card card--${variant}`} style={{ borderRadius, padding, boxShadow: shadow }}>
          {showImage && (
            <div className={`card__image card__image--${imagePosition}`}>
              <img src={imageUrl} alt={imageAlt} />
            </div>
          )}
          <div className="card__content">
            <h2>{title}</h2>
            {subtitle && <p>{subtitle}</p>}
          </div>
          {/* ... more conditional rendering ... */}
        </div>
      );
    }
    

    Warum das Ihre App zerstört:

    • Eine brüchige API-Oberfläche: Jeder neue Anforderung bedeutet das Hinzufügen einer weiteren Eigenschaft, Eingriffe in die Interna der Komponente und das Risiko, dass etwas anderes ausfällt.
    • Überdimensionierte Pakete: Die Komponente muss schließlich Codepfade für Funktionen importieren und rendern, die in der tatsächlichen Nutzung nie benötigt werden.
    • Harte Testabdeckung: Sie müssen eine ständig wachsende Anzahl an Eigenschaftskombinationen überprüfen.
    • Konflikte mit React Server Components: Überladene Eigenschaftslisten neigen dazu, mehr Logik auf die Client-Seite zu verlagern, als eigentlich notwendig ist.

    Die Lösung:

    // ✅ Good: Composition
    function Card({ children, className }) {
      return (
        <div className={clsx('card', className)}>
          {children}
        </div>
      );
    }
    function CardImage({ src, alt, position = 'top' }) {
      return (
        <div className={`card__image card__image--${position}`}>
          <img src={src} alt={alt} loading="lazy" />
        </div>
      );
    }
    function CardContent({ title, subtitle }) {
      return (
        <div className="card__content">
          <h2>{title}</h2>
          {subtitle && <p>{subtitle}</p>}
        </div>
      );
    }
    function CardActions({ children, align = 'end' }) {
      return (
        <div className={`card__actions card__actions--${align}`}>
          {children}
        </div>
      );
    }
    // Usage
    <Card>
      <CardImage src="/photo.jpg" alt="Product" />
      <CardContent title="Headphones" subtitle="Noise cancelling" />
      <CardActions>
        <button>Add to Cart</button>
      </CardActions>
    </Card>
    

    Best Practice für 2026: Verwenden Sie stattdessen das Compound-Component-Muster oder die auf Slots basierende Komposition. Lassen Sie den Verbraucher des Components über Struktur und Inhalt entscheiden – der Component selbst sollte lediglich den Container bereitstellen.

    6. Vermeidung von React.Fragment (Überflüssige Divs)

    Der Fehler: Geschwisterelemente in einen einfachen <div> einpacken, nur um der Regel von JSX gerecht zu werden, dass ein Component einen einzigen Wurzelknoten zurückgeben muss.

    // ❌ Bad: Wrapper div for layout that breaks semantics
    function TableCell({ label, value }) {
      return (
        <div> {/* This div breaks the <table> structure! */}
          <td>{label}</td>
          <td>{value}</td>
        </div>
      );
    }// ❌ Bad: Extra div in a list
    function ListItems({ items }) {
      return (
        <ul>
          {items.map(item => (
            <div key={item.id}> {/* Invalid HTML: div inside ul */}
              <li>{item.name}</li>
              <li>{item.description}</li>
            </div>
          ))}
        </ul>
      );
    }
    

    Warum es Ihre App zerstört:

    • Ungültige Markup-Struktur: Ein <div> ist kein zulässiges Kind von Elementen wie <table>, <ul>, <select> oder <dl>. Browser schreiben den DOM heimlich um, um dies zu beheben, was zu Inkonsistenzen bei der Hydratierung während der Server-Seite-Rendering führt.
    • Störungen im Layout: Zusätzliche Wrapper-Elemente beeinträchtigen das Verhalten von CSS Grid, Flexbox sowie die gap-Eigenschaft.
    • Hydratierungsfehler in React: Angesichts der weit verbreiteten Nutzung von Streaming-SSR und Server Components kann diese Art von ungültiger Verkettung tödliche Inkonsistenzen bei der Hydratierung verursachen, die die App auf der Client-Seite abstürzen lassen.

    Die Lösung:

    // ✅ Good: React.Fragment
    function TableCell({ label, value }) {
      return (
        <>
          <td>{label}</td>
          <td>{value}</td>
        </>
      );
    }
    // ✅ Good: Fragment with key (required in lists)
    function ListItems({ items }) {
      return (
        <ul>
          {items.map(item => (
            <React.Fragment key={item.id}>
              <li>{item.name}</li>
              <li>{item.description}</li>
            </React.Fragment>
          ))}
        </ul>
      );
    }
    

    2026 Best Practice: Verwenden Sie die Abkürzung <> </>, wenn kein Schlüssel benötigt wird, und greifen Sie auf <React.Fragment key={...}> zurück, wenn Sie Arrays von Fragmenten rendern, die einen Schlüssel erfordern. Überprüfen Sie regelmäßig Ihren Komponentenbaum in React DevTools, um unnötige Wrapper-Elemente zu erkennen und zu entfernen.

    7. Rendern falscher Zahlen mit && (versteckte Fehler)

    Der Fehler: Die Abhängigkeit von &&, um etwas bedingungsweise anzuzeigen, ohne die Fälle zu berücksichtigen, in denen der linke Operand auf 0, NaN oder einen leeren String auswertet.

    // ❌ Bad: Shows "0" on screen when items.length is 0
    function Cart({ items }) {
      return (
        <div>
          <h1>Shopping Cart</h1>
          {items.length && (
            <ul>
              {items.map(item => <li key={item.id}>{item.name}</li>)}
            </ul>
          )}
        </div>
      );
    }
    // ❌ Bad: accountBalance could be 0
    function AccountSummary({ account }) {
      return (
        <div>
          {account.balance && <p>Balance: ${account.balance}</p>}
        </div>
      );
    }
    

    Warum das Ihre App kaputt macht:

    • React rendernt tatsächlich 0: Das Schreiben von {0 && <Component />} erzeugt nichts, es wird die numerische Zahl 0 als Text ausgegeben.
    • Unerkannte UI-Fehler: Die Benutzer starren auf ein unerwünschtes „0“ irgendwo auf der Seite, was unordentlich aussieht und das Layout stören kann.
    • Reale Nullwerte: Dinge wie Kontostände, Anzahl von Elementen oder Punktzahlen sind häufig auf null – und das ist völlig normal. Wenn man 0 mit && als falsch behandelt, wird entweder nichts angezeigt, wenn es eigentlich angezeigt werden sollte, oder die Ziffer selbst, wenn man sie eigentlich nicht anzeigen will.

    Die Lösung:

    // ✅ Good: Explicit boolean check
    function Cart({ items }) {
      return (
        <div>
          <h1>Shopping Cart</h1>
          {items.length > 0 && (
            <ul>
              {items.map(item => <li key={item.id}>{item.name}</li>)}
            </ul>
          )}
        </div>
      );
    }
    // ✅ Good: Nullish coalescing for defaults, ternary for conditionals
    function AccountSummary({ account }) {
      return (
        <div>
          {account.balance != null ? (
            <p>Balance: ${account.balance}</p>
          ) : (
            <p>Loading balance...</p>
          )}
        </div>
      );
    }
    

    Best Practice für 2026: Vermeiden Sie grundsätzlich && für die bedingte Darstellung. Verwenden Sie lieber eine ternäre Logik (condition ? : null) oder erzwingen Sie einen expliziten Booleschen Wert mit etwas wie Boolean(count) && .... Fügen Sie einer ESLint-Regel zu Ihrem Projekt hinzu, die gefährliche &&-Muster in JSX erkennt.

    8. Auslassen der Memoisierung bei aufwändigem JSX

    Der Fehler: Ein aufwändiger Unterbaum wird bei jeder Aktualisierung des Elternteils neu gerendert, obwohl die relevanten Props tatsächlich nicht geändert wurden.

    // ❌ Bad: HeavyChart re-renders every time Parent re-renders
    function Dashboard({ data, filter }) {
      const [hoveredItem, setHoveredItem] = useState(null);
    
      return (
        <div>
          <Sidebar onHover={setHoveredItem} />
          <HeavyChart data={data} filter={filter} /> {/* Re-renders on every hover! */}
          <Tooltip item={hoveredItem} />
        </div>
      );
    }
    function HeavyChart({ data, filter }) {
      // Expensive SVG rendering, D3 calculations, etc.
      return <svg>{/* thousands of nodes */}</svg>;
    }
    

    Warum es Ihre App beeinträchtigt:

    • Vergebliche Berechnungen: HeavyChart führt seine Render-Logik erneut aus, selbst wenn data und filter unverändert bleiben.
    • Fallende Frame-Raten: Wenn das Rendern von HeavyChart etwa 50 ms dauert, kann ein einfaches Hover-Event in einer Seitenleiste die Frame-Rate auf etwa 20 fps senken.
    • Beschränkungen des React Compilers: Die automatische Memoisierung durch den Compiler funktioniert innerhalb eines Components, kann aber keine Optimierungen über Component-Grenzen hinweg durchführen, wenn der Elternteil ständig neu erstellte Callback-Referenzen weiterleitet.

    Die Lösung:

    // ✅ Good: Memoize the expensive component
    const HeavyChart = memo(function HeavyChart({ data, filter }) {
      // Expensive rendering
      return <svg>{/* thousands of nodes */}</svg>;
    });
    function Dashboard({ data, filter }) {
      const [hoveredItem, setHoveredItem] = useState(null);
    
      // Stabilize the callback if passed to children
      const handleHover = useCallback((item) => setHoveredItem(item), []);
    
      return (
        <div>
          <Sidebar onHover={handleHover} />
          <HeavyChart data={data} filter={filter} />
          <Tooltip item={hoveredItem} />
        </div>
      );
    }
    

    Best Practice 2026: Der Compiler von React 19 verringert in einfachen Fällen den Bedarf an manueller Memoisierung, doch bei wirklich großen Unterbäumen lohnt es sich weiterhin, die Komponente mit memo zu umhüllen. Verwenden Sie den React DevTools Profiler, um herauszufinden, wo sich die tatsächlichen Engpässe befinden, bevor Sie auf Memoisierung zurückgreifen.

    9. Verbindung von Präsentationslogik und Geschäftslogik

    Der Fehler: Die Erstellung einer einzigen Komponente, die gleichzeitig Daten abruft, diese transformiert, den Formzustand verfolgt, Nebeneffekte ausführt und die Markup-Struktur darstellt.

    // ❌ Bad: God component
    function UserProfile({ userId }) {
      const [user, setUser] = useState(null);
      const [loading, setLoading] = useState(true);
      const [isEditing, setIsEditing] = useState(false);
      const [formData, setFormData] = useState({});
    
      useEffect(() => {
        fetchUser(userId).then(data => {
          setUser(data);
          setFormData(data);
          setLoading(false);
        });
      }, [userId]);
    
      const handleSubmit = async () => {
        await updateUser(userId, formData);
        setIsEditing(false);
      };
    
      if (loading) return <Spinner />;
    
      return (
        <div>
          <h1>{user.name}</h1>
          {isEditing ? (
            <form onSubmit={handleSubmit}>
              <input
                value={formData.name}
                onChange={e => setFormData({...formData, name: e.target.value})}
              />
            </form>
          ) : (
            <button onClick={() => setIsEditing(true)}>Edit</button>
          )}
        </div>
      );
    }
    

    Warum es Ihre App zerstört:

    • Schwer zu testen: Sie können die Ladelogik nicht überprüfen, ohne das gesamte Komponentenobjekt zu initialisieren, und Sie können die dargestellte Benutzeroberfläche nicht testen, ohne die Netzwerkschicht zu simulieren.
    • Nicht wiederverwendbar: Das Verhalten des Formulars ist in dieser einen Komponente festgelegt und kann nicht anderswo genutzt werden.
    • Schwer zu verstehen: Zustand, Nebeneffekte und Markup sind miteinander verknüpft, sodass eine kleine Änderung an der Benutzeroberfläche versehentlich die Logik zur Datenverarbeitung stören kann.
    • Schlechte Integration mit Server Components: Nach den Konventionen von 2026 gehört das Datenabrufen zu Server Components, während Client Components für Interaktivität vorgesehen sind.

    Die Lösung:

    // ✅ Good: Separated concerns
    // Container / Logic Hook
    function useUserProfile(userId) {
      const { data: user, isLoading } = useQuery({
        queryKey: ['user', userId],
        queryFn: () => fetchUser(userId),
      });
    
      const updateMutation = useMutation({
        mutationFn: updateUser,
        onSuccess: () => queryClient.invalidateQueries({ queryKey: ['user', userId] }),
      });
    
      return { user, isLoading, updateUser: updateMutation.mutate };
    }
    // UI Component
    function UserProfile({ userId }) {
      const { user, isLoading, updateUser } = useUserProfile(userId);
      const [isEditing, setIsEditing] = useState(false);
    
      if (isLoading) return <Spinner />;
    
      return (
        <div>
          <h1>{user.name}</h1>
          {isEditing ? (
            <UserProfileForm
              initialData={user}
              onSubmit={(data) => {
                updateUser({ userId, ...data });
                setIsEditing(false);
              }}
              onCancel={() => setIsEditing(false)}
            />
          ) : (
            <button onClick={() => setIsEditing(true)}>Edit</button>
          )}
        </div>
      );
    }
    // Reusable Form Component
    function UserProfileForm({ initialData, onSubmit, onCancel }) {
      const [formData, setFormData] = useState(initialData);
    
      return (
        <form onSubmit={(e) => { e.preventDefault(); onSubmit(formData); }}>
          <input
            value={formData.name}
            onChange={e => setFormData(prev => ({...prev, name: e.target.value}))}
          />
          <button type="submit">Save</button>
          <button type="button" onClick={onCancel}>Cancel</button>
        </form>
      );
    }
    

    Empfohlener Ansatz für 2026: Aufteilen der Verantwortlichkeiten in benutzerdefinierte Hooks für die Logik, einfache Präsentationskomponenten für das Markup sowie Server Components für das Laden von Daten. Eine Komponente sollte entweder eine Aktion ausführen oder etwas anzeigen – es kommt selten vor, dass eine Komponente beides gut bewältigen kann.

    10. Verzicht auf TypeScript-Interfaces für Props

    Der Fehler: Abhängigkeit von prop-types, verstreuten JSDoc-Kommentaren oder, im schlimmsten Fall, völligem Verzicht auf die Typüberprüfung von Props.

    // ❌ Bad: No types
    function Button(props) {
      return <button className={props.variant}>{props.children}</button>;
    }
    // ❌ Bad: PropTypes at runtime only
    import PropTypes from 'prop-types';
    function Button(props) {
      return <button className={props.variant}>{props.children}</button>;
    }
    Button.propTypes = {
      variant: PropTypes.string,
      children: PropTypes.node,
    };
    

    Warum es Ihre App zerstört:

    • Stille Fehler: Tippfehler in Eigenschaftsnamen, fehlende erforderliche Werte sowie nicht übereinstimmende Typen führen zu Laufzeitabstürzen, anstatt bereits vor der Bereitstellung erkannt zu werden.
    • Refactoring wird schwierig: Das Umbenennen einer einzigen Eigenschaft zwingt Sie dazu, den gesamten Codebase manuell durchzusuchen, während ein Typensystem sofort alle Aufrufstellen markiert, die aktualisiert werden müssen.
    • Schwächere Entwicklererfahrung: Ohne Typen verlieren Sie die Autocomplete-Funktion, Hinweise zur Inline-Dokumentation sowie den Schutz durch automatisches Umbenennen.
    • Schlechte Kompatibilität mit React 19: Neuere React-Funktionen wie Server Components, die Verwendung von use mit Promises sowie das Weiterleiten von Referenzen wurden unter Berücksichtigung des Typenmodells von TypeScript entwickelt, wodurch das Auslassen von Typen bedeutet, dass Sie die zur Laufzeit dieser Funktionen notwendigen Garantien verlieren.

    Die Lösung:

    // ✅ Good: Explicit TypeScript interface
    interface ButtonProps {
      variant?: 'primary' | 'secondary' | 'danger';
      size?: 'sm' | 'md' | 'lg';
      disabled?: boolean;
      onClick?: (event: React.MouseEvent<HTMLButtonElement>) => void;
      children: React.ReactNode;
      type?: 'button' | 'submit' | 'reset';
    }
    function Button({
      variant = 'primary',
      size = 'md',
      disabled = false,
      onClick,
      children,
      type = 'button'
    }: ButtonProps) {
      return (
        <button
          type={type}
          className={`btn btn--${variant} btn--${size}`}
          disabled={disabled}
          onClick={onClick}
        >
          {children}
        </button>
      );
    }
    

    Empfohlener Ansatz für 2026: Aktivieren Sie den strengen Modus (strict: true in tsconfig.json). Definieren Sie Modell-Eigenschaften mit interface-Deklarationen anstelle von type-Aliasen, da Interfaces in der Regel klarere Fehlermeldungen liefern und leichter erweiterbar sind. Verwenden Sie React.ComponentProps, wenn Sie beim Erstellen von Wrapper-Komponenten Typen aus nativen HTML-Elementen ableiten müssen. Vermeiden Sie unter keinen Umständen den Einsatz von any für Eigenschaftstypen.

    Zusammenfassung: Die 2026-Komponenten-Checkliste

    Bevor Sie eine Komponente zusammenführen, gehen Sie diese Checkliste durch:

    • [ ] Haben Sie semantisches HTML statt einer Ansammlung generischer <div>-Elemente verwendet?
    • [ ] Sind die Styles in CSS definiert und nicht in Inline-Style-Objekten?
    • [ ] Verwenden die Listeinträge stabile, eindeutige key-Werte?
    • [ ] Werden Props immer als nur zum Lesen behandelt?
    • [ ] Kann die Komponente komponiert werden, anstatt mehr als ein Dutzend Konfigurations-Props zu benötigen?
    • [ ] Haben Sie unnötige Wrapper-Divs durch Fragment ersetzt?
    • [ ] Werden bedingte Darstellungen als Ternäre oder explizite boolesche Prüfungen statt mit && geschrieben?
    • [ ] Wird der aufwändige JSX-Code dort, wo es wichtig ist, gememorisert?
    • [ ] Ist die Geschäftslogik von der Markup-Struktur getrennt und in Hooks oder Hilfsfunktionen untergebracht?
    • [ ] Sind alle Props mithilfe von TypeScript-Interfaces vollständig beschrieben?

    Keiner dieser Fehler wird Ihre Anwendung bereits am ersten Tag zum Absturz bringen. Stattdessen untergraben sie sie allmählich durch zunehmende Barrieren in der Zugänglichkeit, allmählichen Leistungsverlust sowie subtile Zustandskorruptionen. Wenn Sie sie jetzt beheben, bleibt Ihre React-Codebasis auch bei der nächsten Framework-Upgrade, der nächsten umfassenden Refaktorierung sowie bei der Übernahme durch ein anderes Team intakt.