React 19 forwardRef Migration: ref als Prop in TypeScript

Deine Komponenten stecken voller forwardRef-Wrapper, displayName-Zeilen und ComponentPropsWithoutRef-Typen, und React 19 sagt, dass du nichts davon mehr brauchst. Die Änderung rund um React 19 forwardRef ist auf dem Papier klein: Function Components bekommen ref jetzt als normale Prop. Die Arbeit steckt in den Randfällen. Props-Typen, die den Ref entfernen, Komponenten, die den DOM-Knoten selbst brauchen, und asChild-Wrapper, die den Ref an ein Kind weitergeben, verhalten sich ohne Wrapper alle ein bisschen anders. Dieses Tutorial migriert ein Input und einen Button, geprüft mit React 19.3 und TypeScript 5.9, und zeigt die Muster, die wir in Wingo UI nutzen. Dessen Quellcode ruft forwardRef kein einziges Mal auf.
Was hat sich an forwardRef in React 19 geändert?
Function Components bekommen ref in ihren Props, also hat der Wrapper nichts mehr zu tun. Der Release-Post zu React 19 (opens in a new tab) erklärt, dass neue Function Components forwardRef nicht mehr brauchen und dass React es in künftigen Versionen als veraltet markieren und entfernen wird. Die forwardRef-Referenz (opens in a new tab) sagt dasselbe: nicht mehr nötig, in einem späteren Release deprecated.
Wer forwardRef ersetzen will, nimmt also einfach die ref-Prop. Vier Fakten bestimmen, wie du migrierst:
- forwardRef funktioniert weiter und bleibt still. Wir haben die Development-Builds von
reactundreact-dom19.3.0 nach einer Deprecation-Meldung durchsucht und keine gefunden. Alte Komponenten laufen weiter, während du migrierst. - Class Components haben sich nicht geändert. Ein Ref, den du an eine Klasse übergibst, zeigt weiterhin auf die Instanz und taucht nie in ihren Props auf.
element.refist auf dem Rückzug. React 19 füllt es noch, aber wer es im Development-Modus liest, bekommt die MeldungAccessing element.ref was removed in React 19. ref is now a regular prop.Lies stattdessenelement.props.ref.- React 18 entfernt
refaus den Props. Eine Bibliothek, die React 18 noch als Peer Dependency führt, mussforwardRefbehalten. Radix tut das:@radix-ui/react-slot1.3.3 hüllt seinenSlotinReact.forwardRefund akzeptiert React 16.8 bis 19. Wingo UI setzt React 19 voraus, deshalb nehmen seine Komponentenrefals ganz normale Prop.
Wie migrierst du eine Komponente von forwardRef zu ref als Prop?
Lösch den Wrapper, typisier die Props mit ComponentProps und lass den Ref in ...props mitlaufen. So sieht das klassische Input im shadcn-Stil vorher aus:
Und nachher:
ComponentProps<"input"> enthält bereits ref?: Ref<HTMLInputElement>. Der Spread gibt ihn also an das <input> weiter, und <Input ref={inputRef} /> funktioniert ohne eine einzige Zeile speziell für den Ref. Eine benannte Funktion liefert den React DevTools ihren Namen, also fällt auch displayName weg.
Für einen ganzen Ordner startest du mit dem offiziellen Codemod aus reactjs/react-codemod (opens in a new tab), auf einem sauberen Branch, damit du den Diff lesen kannst:
shadcn/ui hat denselben Schritt gemacht, als es auf React 19 umgestiegen ist. Der shadcn-Guide zu Tailwind v4 (opens in a new tab) listet die manuellen Schritte: React.forwardRef<...> durch React.ComponentProps<...> ersetzen, ref={ref} entfernen, ein data-slot-Attribut ergänzen, in eine benannte Funktion umbauen und displayName streichen. Der aktuelle Button dort typisiert seine Props als React.ComponentProps<"button"> plus Varianten und asChild. Stecken in deinem Projekt noch ältere shadcn forwardRef-Komponenten mit lokalen Änderungen, erklärt shadcn-Komponenten aktualisieren, ohne Änderungen zu verlieren den Merge.
Damit forwardRef nicht zurückkommt, ergänzt du eine Lint-Regel. Stand Oktober 2026 steckt no-forward-ref von eslint-react (opens in a new tab) in den empfohlenen Presets, und noReactForwardRef von Biome (opens in a new tab) (seit Biome 2.2.5) ist opt-in und bringt einen als unsicher markierten Fix mit. In Paketen, die noch React 18 unterstützen, bleiben beide aus.
Wie typisierst du ref als Prop in React 19 mit TypeScript?
Meistens musst du gar nichts typisieren, denn ComponentProps<"button"> enthält ref schon. ref?: Ref<HTMLButtonElement> ergänzt du nur, wenn du den Props-Typ selbst schreibst. Die übrigen Typänderungen sind Löschungen und Umbenennungen:
Über die letzte Zeile stolpern viele an dem Tag, an dem sie @types/react aktualisieren. Ref-Callbacks dürfen jetzt eine Cleanup-Funktion zurückgeben. Eine Arrow Function, die den Knoten implizit zurückgibt, scheitert deshalb mit Type 'HTMLDivElement | null' is not assignable to type 'void | (() => VoidOrUndefinedOnly)'. Setz geschweifte Klammern, und es kompiliert.
Wenn die Props deine eigenen sind, deklarierst du den Ref wie jede andere Prop:
Generische Komponenten bekommen ihren Typparameter zurück
Am meisten profitieren generische Komponenten von ref als Prop in React 19. forwardRef gibt eine Komponente zurück, die nicht generisch ist. Eine typisierte Liste brauchte deshalb einen Cast, um ihr <T> zurückzubekommen:
Jetzt ist es eine ganz normale generische Funktion. T wird an der Aufrufstelle inferiert, und der Ref bleibt typisiert:
Wrapper-Schichten reichen den Ref ohne Extra-Code weiter
In unserem Button taucht der Ref nie namentlich auf. ButtonProps ist Omit<ComponentProps<"button">, "color" | ...> plus unsere eigenen Props. Die Komponente liest sie über useDefaults("button", buttonProps), das app-weite Defaults aus UIProvider unter die übergebenen Props legt, und spreadet den Rest auf motion.button. Mit forwardRef käme der Ref als zweites Argument an, und jede Schicht zwischen Aufrufer und Element bräuchte einen eigenen Weg, ihn mitzunehmen. Als Prop ist er einfach ein Key mehr.
Wie führst du Refs in React 19 zusammen?
Du führst die beiden Refs in einem memoisierten Callback-Ref zusammen, der ein Cleanup zurückgibt. Refs zusammenführen musst du, wenn die Komponente den DOM-Knoten selbst nutzt und der Aufrufer ihn auch will. Unser Input ist der echte Fall. Es braucht den Knoten, um den Text mit einem echten Input-Event zu leeren, den Fokus danach zurückzusetzen und Formular-Resets mitzubekommen. Dein Code braucht denselben Knoten, um das Feld nach einem fehlgeschlagenen Absenden zu fokussieren. Das ist das Muster aus components/ui/input.tsx, gekürzt auf ein Suchfeld:
Drei Details sind wichtig:
- Cleanups. Wenn der Knoten verschwindet, ruft React 19 das Cleanup auf, das ein Ref-Callback zurückgegeben hat, statt den Callback noch einmal mit
nullaufzurufen.attachRefführt deshalb das Cleanup des Aufrufers aus, wenn er eines zurückgegeben hat, und fällt sonst aufref(null)zurück. - Ein stabiler Callback.
useCallbackhältsetRefzwischen Renders gleich. Ein neuer Callback-Ref bei jedem Render lässt React ihn jedes Mal lösen und neu anhängen. In einem Test unter StrictMode hat ein Inline-Ref-Callback nach einem einfachen Re-Render sein Cleanup und ein frisches Anhängen geloggt, während der memoisierte angehängt blieb. - Ein Leeren, das React mitbekommt.
clearValuegeht über den nativen Value-Setter und feuert eininput-Event. So läuft dasonChangedes Aufrufers mit einem leeren Wert, wie nach einem Tastendruck. Werinput.value = ""zuweist, leert das Feld, ohne React oder eine Formular-Bibliothek zu informieren.
Textarea führt Refs genauso zusammen, damit sie ihren eigenen Knoten messen und mit dem Inhalt wachsen kann.
Von außen nutzt du es wie einen normalen Ref:
register aus React Hook Form gibt einen Ref auf dieselbe Weise weiter, also erreicht {...register("email")} das <input> ohne Adapter. Welche Felder stattdessen Controller brauchen, steht im Guide zu React-Formular-Komponenten.
$ npx wingo-ui@latest add inputWenn du Methoden statt eines Knotens nach außen gibst, funktioniert useImperativeHandle(ref, ...) genau wie vorher, nur mit ref aus den Props. Unser Pro Schema Builder gibt so validate() und getJsonSchema() heraus; was Pro enthält, siehst du unter Preise.
Wie funktioniert ref als Prop mit asChild und Slot?
Spreade die Props samt Ref auf Slot und setz den Ref auf das Kind, wenn du dessen Typ brauchst. Radix Slot kombiniert den Ref, den es bekommt, mit dem eigenen Ref des Kindes. In React 19 liest es den Ref des Kindes aus element.props.ref, also geht keiner verloren, und es erscheint keine Warnung.
Die Falle ist TypeScript. Eine Button-Komponente typisiert ihren Ref für <button>, also scheitert ein Link-Ref am Wrapper:
Setz den Ref auf das Kind. Slot führt ihn mit seinem eigenen zusammen, und der Typ stimmt:
Unser Button rendert asChild über motion.create(Slot), also wollen zwei Refs denselben Knoten: der von motion für die Press-Feder und deiner. Slot kombiniert beide. Wir haben diesen Button in Chromium unter StrictMode mit einem einfachen Anchor als Kind gemountet: Der Ref des Kindes bekam das <a>, und die Konsole blieb leer.
$ npx wingo-ui@latest add buttonZwei weitere Dinge machen asChild nach einer Migration kaputt:
- Eine Kind-Komponente, die den Ref verliert. Slot gibt
ref,classNameund Event-Handler an das Kind weiter, und eine Komponente, die ihre Props nicht spreadet, verliert alle drei. Radix Tooltips und Popover finden ihren Trigger über diesen Ref. Ohne ihn hat der Inhalt kein Element, an dem er sich positionieren kann. - Ein selbstgebauter Slot, der
child.refliest. Zur Laufzeit funktioniert das noch, aber im Development-Modus loggt er denelement.ref-Fehler, und@types/react19 deklariertrefnicht mehr aufReactElement, also lehnt TypeScript es ebenfalls ab. Lies den Ref aus den Props, mit einem Typargument, weil Element-Props in React 19 standardmäßigunknownsind:
Was solltest du als Nächstes ausprobieren?
Installier die beiden kostenlosen Komponenten aus diesem Post und schau dir in deinem eigenen Projekt an, wie sie mit Refs umgehen:
Beide landen als Quellcode, der dir gehört, in components/ui, zusammen mit Field, Spinner und den Helfern, die sie importieren, und keine dieser Dateien nutzt forwardRef. Für die andere Hälfte des Stacks, auf den sie ausgelegt sind, stöber in den Guides zu Next.js und Tailwind CSS, am besten zuerst in Tailwind v4 Design Tokens.
Häufige Fragen
Ist forwardRef in React 19 deprecated?
Nicht offiziell. Laut React-Doku ist forwardRef in React 19 nicht mehr nötig und wird in einem künftigen Release als veraltet markiert. Stand React 19.3 funktioniert es noch und loggt keine Warnung, neue Function Components sollten ref aber als Prop nehmen.
Wie typisiere ich die ref-Prop in React 19 mit TypeScript?
Wenn du ein einzelnes Element umhüllst, nimm ComponentProps<'button'>, das ref schon enthält. Schreibst du den Props-Typ selbst, ergänze ref?: Ref<HTMLButtonElement>. Verwende ComponentPropsWithoutRef nicht mehr für Wrapper und ersetze ElementRef durch ComponentRef.
Brauche ich forwardRef noch, wenn meine Bibliothek React 18 unterstützt?
Ja. React 18 entfernt ref aus den Props, bevor deine Komponente läuft, dort bekommst du den Ref also nur über forwardRef. Behalte es, bis React 18 aus deinen Peer Dependencies verschwindet; forwardRef-Komponenten funktionieren auch in React 19.
Funktioniert asChild noch ohne forwardRef?
Ja. Radix Slot liest in React 19 den Ref des Kindes aus dessen Props und kombiniert ihn mit dem Ref, den der Slot bekommt. Setz den Ref auf das Kind, wenn du dessen echten Typ brauchst, etwa HTMLAnchorElement für einen Link, der über eine Button-Komponente gerendert wird.
Gibt es einen Codemod, der forwardRef entfernt?
Ja. Das Projekt reactjs/react-codemod liefert remove-forward-ref: Führ npx codemod react/19/remove-forward-ref --target components/ui auf einem sauberen Branch aus und prüf danach den Diff, vor allem generische Komponenten und Props-Typen, die noch ComponentPropsWithoutRef nutzen.
Bekommen Class Components in React 19 ref als Prop?
Nein. Ein Ref, den du an eine Class Component übergibst, zeigt weiterhin auf die Instanz der Komponente und landet nicht in ihren Props.
- React 19
- TypeScript
- forwardRef
- shadcn/ui
- Refs


