16. Juni 2026
Igitt, das stinkt! react-stinky ist da
Ein Code Smell ist das React-Äquivalent zu Milch, die übers Wochenende gekippt ist: Sie gießt sich noch, aber irgendwas stimmt nicht. react-stinky ist ein Agent Skill, der durch deine komplette Komponente, deinen Hook oder dein Modul geht, die Kosten jedes Smells benennt, einen konkreten Fix mit Quelle vorschlägt und (der Teil, der ihn brauchbar macht) weiß, wann er den Mund halten soll. Hier ist, wonach er schnüffelt, wie er den Mief bewertet und warum die Liste der Dinge, die er nicht meldet, wichtiger ist als die Liste der Dinge, die er meldet.
Sascha Becker
Author10 Min. Lesezeit

Igitt, das stinkt! react-stinky ist da
HABEN SIE ES SATT, eine Komponente zu öffnen und vom Mief umgehauen zu werden? Hat Ihr <Stack> ein onClick, das kein Tastaturnutzer je erreicht? Vermehren sich Ihre Boolean-Props wie die Karnickel, isSmall neben isLarge neben isError neben isLoading, bis keiner mehr weiß, welche Kombination überhaupt erlaubt ist? Leute, das kennen wir ALLE. Am Freitag ging der Code sauber raus. Am Montag STINKT er. Aber was, wenn es einen Weg gäbe, den Mief aufzuspüren, bevor es Ihr Reviewer tut? Hier kommt react-stinky, der Code-Smell-Detektor, der durch Ihre ganze Komponente läuft, sich die Nase zuhält und Ihnen genau sagt, was da gestorben ist und wie man es beerdigt. Er schneidet. Er hackt. Er zitiert die React-Docs. Aber Moment, es kommt noch besser.
Okay. Teleshopping-Stimme aus.
react-stinky ist ein neuer Agent Skill in meinem offenen Set, und unter dem Dauerwerbesendungs-Lack steckt ein ernsthaftes Werkzeug, gebaut um eine Bedingung: Ein Smell-Detektor ist nur so gut wie die Dinge, über die er schweigt. Hier ist, was er tut, was er sich weigert zu tun, und warum diese zweite Liste der Teil ist, der ihn behaltenswert macht.
Wonach er wirklich schnüffelt
react-stinky ist ein ganzheitlicher Code-Smell-Detektor für React und TypeScript. Das Wort, das sich hier verdient, ist ganzheitlich. Eine normale Lint-Regel betrachtet eine Zeile isoliert. react-stinky liest die ganze Komponente, den Hook, das Modul, und stellt die Fragen, die ein gründlicher Reviewer stellt. Lässt sich dieser State aus Props ableiten, statt ihn zu speichern? Macht dieser Effect einen Job, der in einen Event-Handler gehört? Kann ein Tastaturnutzer das hier überhaupt auslösen? Erlaubt diese Prop-Form einen State, der unmöglich sein sollte?
Für jeden Smell, den er findet, tut er drei Dinge, die ein Linter normalerweise nicht tut:
- Er benennt die Kosten. Nicht "vermeide das", sondern "ein zusätzlicher Render und ein State-Wert, der von seinen Inputs abdriften kann". Du bekommst den echten Preis und entscheidest selbst, ob er ihn wert ist.
- Er schlägt einen konkreten Fix vor, vorher und nachher, in der Sprache der Datei.
- Er verlinkt die Quelle. React-Docs, MDN, das TypeScript-Handbuch, MUI. Jedes Finding ist ein Argument mit Beleg, keine Geschmacksfrage.
Die acht Säulen
Die Abdeckung umfasst acht Säulen. Sieben davon arbeiten auf einer einzelnen Datei:
- Komponenten-API und Props, das Rückgrat. Benennung, Boolean- und Callback-Konventionen, String-Unions statt Boolean-Flags, Discriminated Unions, die unmögliche States verbieten, controlled und uncontrolled State, Slots und Children-Komposition, Generics, Refs, Styling-APIs, Accessibility-Props, Server-Component-Grenzen, JSDoc.
- State und Datenfluss. Ableitbare Werte in
useState, Props, die in State kopiert werden und dann abdriften, dieselbe Tatsache an zwei Stellen gespeichert, Prop Drilling durch Schichten, die sie nur weiterreichen. - Effects und Lifecycle. Effects, die abgeleitete Daten berechnen, Fetches und Subscriptions und Timer ohne Cleanup (Races und Leaks), Dependency-Arrays, die lügen über das, was der Effect liest.
- Komponentenstruktur und Hooks. Gott-Komponenten, die Fetching, Logik und Darstellung auf einmal machen; eine Komponente, die in einer anderen definiert ist (bei jedem Render ein brandneuer Typ); konditionale Hooks.
- Render-Korrektheit. Array-Index als
keyauf einer Liste, die umsortiert oder editiert; direkte Mutation von State oder Props; verschachtelte Ternary-Suppe im JSX; kopierte Blöcke, die einen parametrisierten Helper wollen. - Accessibility im Markup.
onClickauf einem<div>ohne Rolle, ohnetabIndexund ohne Tastatur-Handler; Div-Suppe, wo semantische Elemente hingehören; Formularfelder ohne zugeordnetes Label. - TypeScript-Disziplin.
anyundas anyund@ts-ignore, lügendeas-Casts, Non-Null-!auf einem Wert, der null sein kann.
Die achte Säule, dateiübergreifende Duplikation, läuft nur im Ordner- und Repo-Scope, weil sie Dateien gegeneinander vergleicht: eine wiederverwendbare Komponente, die woanders inline nachgebaut ist, ein Hook, der kopiert statt geteilt wird, ein Typ, der an zwei Stellen deklariert ist und leise auseinanderdriftet.
Wie er den Mief bewertet
Nicht jeder Smell ist ein Notfall, und ein Werkzeug, das alle gleich behandelt, bringt dir bei, es zu ignorieren. react-stinky sortiert jedes Finding in drei Stufen.
Die drei Stufen des Gestanks
Rancid (ranzig). Ein echter Bug oder ein Bruch in Korrektheit,
Accessibility oder an der Server-Grenze. Jetzt fixen. Ein Control, das keine
Tastatur erreicht, ein mutiertes State-Array, value || 50, das leise
value={0} frisst, eine Funktion über die Server-Grenze gereicht.
Funky. Eine echte Wartbarkeitsbremse, kein Bug. Sollte gefixt werden. Eine Boolean-Explosion, die eine Union will, eine Gott-Komponente, ein Effect, der Daten berechnet, die du beim Render ableiten könntest.
Whiff (ein Hauch). Klein oder stilistisch. Optional. Ein nacktes
loading-Prop, JSDoc, das nur den Namen wiederholt. Real, aber kein Grund,
eine Änderung zu blockieren.
Die Stufe ist der Punkt. Sie ist der Unterschied zwischen einem Report, den du von oben bis unten abarbeitest, und einem, den du überfliegst und schließt.
Der Teil, der ihn brauchbar macht
Das ist die These, also bekommt sie ihren eigenen Abschnitt. Jeder kann einen Checker schreiben, der jedes onClick, jedes any und jeden Index-Key meldet. Zurück bekommst du eine Wand aus Lärm, die du wegzuscrollen lernst, und das ist schlimmer als gar kein Werkzeug, weil die echten Probleme jetzt unter den falschen begraben liegen.
react-stinky trägt eine Schutzleine. Jeder Smell im Katalog hat eine "Nicht melden"-Zeile, und eine Handvoll Regeln zieht sich durch alle.
- Native HTML-Attribute bleiben nackt. Er sagt dir nicht, du sollst
disabledinisDisabledumbenennen oderonChangean einem echten<input>inonValueChange. - Etablierte Library-Konventionen sind keine Smells. MUIs
open,slotsundsx; Radix'asChild. Spricht die Datei diesen Dialekt schon, spricht react-stinky ihn zurück, statt ihn zu korrigieren. - Config-Objekt-Props sind richtig für datengetriebene Komponenten mit fester Anordnung, etwa ein Data Grid. Nicht jedes Config-Array will Compound Components werden.
- Ein Effect ist das richtige Werkzeug für echte externe Synchronisation. Er meldet den Effect, der abgeleiteten State berechnet, nicht den, der mit einem Websocket, localStorage oder einem Nicht-React-Widget spricht.
- Index-Keys sind in Ordnung auf einer statischen Liste, die nie umsortiert, einfügt oder löscht. Der Bug taucht erst auf, wenn sich Items bewegen.
- Ein Finding pro echtem Problem, und der kleinste Fix, der den Smell entfernt. Eine konsistente lokale Konvention schlägt den Katalog-Standard.
Was er bewusst nicht anfasst
react-stinky hat Nachbarn, und er bleibt aus deren Gärten. Zwei Themen reicht er absichtlich weiter:
- Memoisierung (
useMemo,useCallback,React.memo) geht an denreact-compiler-Skill. - Farbliterale gehen an
theme-colors.
Sind diese Skills nicht installiert, notiert react-stinky das Finding in einer Zeile und geht weiter. Er baut sie nicht nach, und er bläht seinen Report nicht mit einer Kategorie auf, die ein anderes Werkzeug besitzt. Ein Skill, der alles versucht, kann nichts richtig, also zieht dieser eine harte Kante um React- und TypeScript-Wartbarkeit und hört dort auf.
Passe den Scope an die Frage an
Du richtest react-stinky auf so viel oder so wenig, wie du willst, und er passt die Arbeit an die Frage an.
| Scope | Auslöser | Was er liest |
|---|---|---|
| Fragment | eine eingefügte Funktion | nur diese Fläche, mit Angabe, was er über alles Off-Screen annahm |
| Datei | eine oder mehrere genannte Dateien | jede Datei vollständig, jede Komponente, jeden Hook, jedes Prop-Interface |
| Ordner | ein Verzeichnis | den Ordner, plus den dateiübergreifenden Duplikations-Durchlauf |
| Repo-Sweep | "smell-check die Codebasis" | die Komponenten, Hooks und Module, mit Vorrang für geteilten und exportierten Code |
Eine Ehrlichkeitsregel ist eingebaut. Im Einzeldatei- oder Fragment-Scope, wo er andere Dateien nicht sehen kann, sagt er dir, dass dateiübergreifende Duplikation nicht geprüft wurde, statt zu suggerieren, der Code sei einzigartig. Das Werkzeug sagt, was es nicht angeschaut hat, nicht nur, was es gefunden hat.
Wie ein Report aussieht
Lass ihn laufen, und du bekommst Findings nach Datei sortiert, jedes mit Stufe, Ort, Kosten, einem Vorher-Nachher-Fix und einer Quelle.
textReact Stinky report, src/components/SeedRow.tsx[Rancid] clickable-nonsemantic (a11y markup), Zeile 297Smell: ein <Stack> (rendert ein div) hat onClick, aber keine role, tabIndex oder Tastatur-Handler.Kosten: Tastatur- und Screenreader-Nutzer können es nicht auslösen; für assistive Technik unsichtbar.Fix: ein echtes Control rendern (component="button" oder eine IconButton), oderrole="button" tabIndex={0} und ein onKeyDown für Enter und Space ergänzen.Quelle: MDN button role[Funky] effect-for-derived (state and effects), Zeile 40Smell: ein useEffect plus setState berechnet `fullName` aus `first` und `last`.Kosten: ein zusätzlicher Render und ein State-Wert, der von seinen Inputs abdriften kann.Fix: beim Render berechnen, const fullName = `${first} ${last}`. Effect und State löschen.Quelle: React, You Might Not Need an EffectSummary: 1 rancid, 1 funky across 1 file.
Übersteht nichts die Schutzleine, sagt er es klar: "Smells fresh. No maintainability smells found." Ein sauberes Gutachten ist ein Ergebnis, kein Versagen bei der Arbeitssuche.
Woher die Regeln kommen
Die Regeln, die react-stinky durchsetzt, sind nicht für den Anlass erfunden. Der Großteil von Säule 1, dem Komponenten-API-Rückgrat, kommt direkt aus Can't Maintain, einem Spiel, das ich gebaut habe und das den Blick für langlebige React-APIs schult. Es stellt zwei Versionen derselben Komponente nebeneinander, du wählst die, die besser altert, und es sagt dir warum, mit Link zur React-, TypeScript-, MDN- oder MUI-Quelle, die den Fall belegt.
react-stinky ist die andere Hälfte dieser Idee. Das Spiel bringt einem Menschen bei, einen Smell nach dem anderen zu erkennen. Der Skill nimmt denselben Katalog und lässt ihn auf einmal über eine ganze Datei oder ein ganzes Repo laufen, sodass du nicht jeden einzeln von Hand findest. Das eine schult das Auge, das andere macht den Durchlauf, und beide zitieren dieselben Quellen, damit der Fall nie bloß Geschmack ist.
Aus Can't Maintain ist inzwischen Cant geworden, ein Hub aus Vergleichsspielen, der über React hinaus inzwischen TypeScript, Git, SEO, Testing und UX erreicht. Wenn ein Smell-Report dir Lust macht, diese Dinge schneller zu fangen, bevor sie rausgehen, ist das Spiel der Ort zum Üben. Es gibt mehr über Cant auf der Projektseite.
Probier ihn aus
react-stinky installiert in jeden Agenten, der das skills.sh-Format spricht (Claude Code, Cursor, Codex, Cline, Windsurf, OpenCode):
bashnpx skills@latest add saschb2b/skills --skill react-stinky
Dann richte ihn auf eine Komponente, einen Ordner oder das ganze Repo und bitte ihn zu schnüffeln.
Dafür gibt es einen Skill
Der komplette Katalog, die Stink-Bewertungen und jede "Nicht melden"-Regel
leben als Agent Skill. Installiere ihn mit npx skills@latest add saschb2b/skills --skill react-stinky oder lies den vollständigen
react-stinky-Skill.
Quellen
- Can't Maintain
Das Vergleichs-Quiz für React-APIs, aus dem der Katalog destilliert ist. Wähle die Version, die besser altert, lerne warum, mit der Quelle, die den Fall belegt.
- saschb2b/skills
Das offene Skills-Repo. react-stinky liegt unter engineering, einzeln oder mit dem ganzen Set installierbar.
- skills.sh
Der Installer und das Registry, das einen Skill-Ordner über Claude Code, Cursor, Codex, Cline, Windsurf und OpenCode hinweg funktionieren lässt.
- React, You Might Not Need an Effect
Die kanonische Quelle hinter den Effect- und State-Säulen: beim Render ableiten, Effects für echte externe Synchronisation behalten.
- MDN, die button role
Die Accessibility-Referenz hinter der Markup-Säule: ein klickbares Element braucht eine Rolle, einen Tab-Stopp und Tastaturbedienung.
