Anzeige
Anzeige
Anzeige
Anzeige
Anzeige
Anzeige
Lesedauer 13 Min.

Volljährig – aber auch erwachsen?

React 19 ist da. Was gibt es Neues, und lohnt sich das Upgrade?
© dotnetpro
Unter den Single-Page-Application-Frameworks hat sich React [1], von Facebook beziehungsweise Meta gesteuert, in den letzten Jahren vom Geheimtipp zum Favoriten gemausert. War das von Google vorangetriebene Angular einst unangefochtener Platzhirsch, lautet zumindest bei Google Trends [2] (Bild 1) und Stack Overflow Trends [3] (Bild 2) die Reihenfolge nun React – Angular – Vue.js. Wer genau hinsieht, erkennt in Bild 2 sogar Blazor, in etwa auf dem Niveau von Vue.js.
Siegertreppchen: React, Angular und Vue.js bei Google Trends (Bild 1) © Autor
Spitzengruppe: Diverse SPA-Frameworks bei Stack Overflow Trends (Bild 2) © Autor
Auch für die Webentwicklung mit .NET ist React somit eine ernst zu nehmende Technologie. REST, JSON und meinetwegen sogar GraphQL sei Dank ist es der Clienttechnologie egal, womit das API auf dem Server implementiert worden ist. Für viele Entwickler:innen besonders angenehm ist bei React der Releasezyklus. Bei Angular beispielsweise wird ­alle sechs Monate eine neue Hauptversion veröffentlicht, Blazor macht das jährlich. Das erzeugt auf der einen Seite Planbarkeit, zwingt aber zum anderen regelmäßig dazu, auf die nächste Version zu gehen. React hingegen veröffentlicht neue Versionen erst, „wenn es passt“. Dies zeigt ein Blick auf die vorangegangenen Releases [4] des Frameworks:
  • React 16 erschien im September 2017 und führte insbesondere Hooks ein, ohne die eine Entwicklung mit React heutzutage kaum mehr vorstellbar ist. Ebenso wurden unter anderem seit dieser Version Server Components unterstützt.
  • React 17 wurde gut drei Jahre später veröffentlicht, im Oktober 2020.
  • Bis React 18 vergingen nur anderthalb Jahre, im März 2022 war es so weit. Das ist ein sportliches Tempo, insbesondere unter dem Blickwinkel, dass dank der neuen Rendering-Engine sehr viel unter der Haube aktualisiert wurde.
  • In den folgenden zwei Jahren erschienen einige Minor-Versionen von React 18.x. Im April 2024 wurde Version 18.3.0 veröffentlicht. Diese war identisch zum Vorgänger 18.2.0, gab allerdings Warnungen aus, wenn Features benutzt wurden, die in React 19 nicht mehr funktionieren würden. React 18.3.0 ist damit eine Vorbereitungsversion für eine Migration zur nächsten Hauptversion.
Ebenfalls im April 2024 gab es eine Vorankündigung für ­React 19 – nach zweieinhalb Jahren der erste Major Release. Diese Ankündigung [5] wurde acht Monate später angepasst, denn letztendlich war der Veröffentlichungstag der 5. Dezember 2024. So ein neuer Release sorgt natürlich für viel Vorfreude, doch es bedeutet bei React nicht, dass damit alte Versionen in die Tonne gehören. Die Versionierungs-Po­licy von React [6] besagt, dass alle (!) Hauptversionen mit Sicherheitspatches versorgt werden. Die aktuellste Ver­sion wiederum wird aktiv gepflegt. React unterscheidet hier zwischen „Active Support“ (für den neuesten Release) und „Security Support“ (für alle Versionen). Theoretisch können also auch Uralt-Releases weiterhin betrieben werden. Gerade durch die permanent stattfindenden internen Optimierungen ist der Einsatz der aktuellsten Version jedoch in vielen Fällen empfehlenswert – von den neuen Features ganz zu schweigen!Und gerade um die Neuerungen soll es hier ­gehen. Wir werfen einen Blick auf die wichtigsten neuen Features und Möglichkeiten in React 19. Dies kann als Entscheidungshilfe dienen, ob sich ein Upgrade lohnt. Aus der eigenen Praxis lautet die Antwort in fast allen Fällen Ja – aber entscheiden Sie selbst! Die Codebeispiele in den Downloads zum Artikel liegen als React-Projekt vor. Zur Ausführung benötigen Sie eine aktuelle Version von Node.js und den Node Package Manager (NPM). Bild 3 zeigt, wie Sie die Anwendung starten können: Zunächst installieren Sie einmalig mit npm ci die notwendigen Bibliotheken und Abhängigkeiten. Dann startet npm run dev einen Test-Webserver auf Port 3000, über den Sie dann die einzelnen Beispiele ausführen können. Der URL ist in der Regel http://localhost:3000/xyz, um das Beispiel im Ordner /app/xyz im Browser laden zu können.
Eins, zwei, React: Die Beispiele auf dem eigenen System ausführen (Bild 3) © Autor

use()

Wie bereits zuvor erwähnt, wurden in React 16 – das ist zum Erscheinungszeitpunkt dieser dotnetpro-Ausgabe fast acht Jahre her – Hooks eingeführt. Das sind vereinfacht gesagt Funktionen, deren Name mit use beginnt und mit denen ­bestimmte React-Features verwendet werden können, beispielsweise Zustandsmanagement. Jetzt gibt es in React 19 eine neue Funktion, die „nur“ use() heißt. Ist das eine Art Über-Hook? Nun ja, React selbst nennt use() ein „API“. Die Beschreibung ist recht profan: Mit use() können wir den Wert einer Ressource auslesen; Beispiele hierfür sind React-Kontexte und Promises. Besonders letzteres Beispiel ist interessant, denn gerade beim Aufrufen von HTTP-Ressourcen war bisher ein wenig Boilerplate-Code notwendig.Hier ein einfaches Beispiel: Eine React-Webanwendung will ein API aufrufen und dessen Daten anzeigen. Wir verwenden im Beispiel ein öffentliches Zitate-API [7] mit mehr oder weniger inspirierenden Sprüchen. Der klassische Weg, dies zu implementieren, sieht ungefähr wie folgt aus: Wir erstellen eine Komponente – als Name wählen wir vorausschauend WithoutUseApp – und integrieren diese in unser HTML-UI:

<span class="hljs-keyword">import</span> WithoutUseApp <span class="hljs-keyword">from</span> <span class="hljs-string">"@/components/WithoutUseApp"</span>;
<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Home</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">{styles.page}</span>&gt;</span></span>
<span class="xml">       <span class="hljs-tag">&lt;<span class="hljs-name">main</span> <span class="hljs-attr">className</span>=<span class="hljs-string">{styles.main}</span>&gt;</span></span>
<span class="xml">         <span class="hljs-tag">&lt;<span class="hljs-name">h1</span>&gt;</span>WithoutUseApp<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span></span>
<span class="xml">         <span class="hljs-tag">&lt;<span class="hljs-name">WithoutUseApp</span> /&gt;</span></span>
<span class="xml">      <span class="hljs-tag">&lt;/<span class="hljs-name">main</span>&gt;</span></span>
<span class="xml">    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>);
} 
In der Komponente selbst ziehen wir alle Register und setzen auf zwei Hooks:
  • Mit useState() implementieren wir das Zustandsmanagement, also vereinfacht gesagt: Wir merken uns bestimmte Werte, wie etwa die Rückgaben des API.
  • Mit useEffect() sorgen wir dafür, dass beim Rendern der Komponente das API aufgerufen wird.
Der Rahmen unserer Komponente steht also:

<span class="hljs-string">"use client"</span>;
<span class="hljs-keyword">import</span> { useState, useEffect } <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>;
<span class="hljs-keyword">const</span> WithoutUseApp = <span class="hljs-function"><span class="hljs-params">()</span> =&gt;</span> {
  <span class="hljs-comment">// ...</span>
<span class="hljs-comment">}</span> 
Zunächst müssen wir uns um die Zustandsinformationen kümmern. Wir legen per useState() die entsprechenden Variablen und Setter-Funktionen für die drei Werte an, die sich ändern könnten:
  • das Zitat des API,
  • der Ladezustand (lädt gerade – hat geladen),
  • die Exception, falls beim Aufruf des API ein Fehler auftreten sollte.
Im Code sieht das wie folgt aus:

<span class="hljs-keyword">const</span> [quote, setQuote] = useState(<span class="hljs-keyword">null</span>);
<span class="hljs-keyword">const</span> [loading, setLoading] = useState(<span class="hljs-keyword">true</span>);
<span class="hljs-keyword">const</span> [<span class="hljs-keyword">error</span>, setError] = useState(<span class="hljs-keyword">null</span>); 
Das Laden des API geschieht in mehreren Schritten. Zunächst führen wir einen HTTP-Request aus und rufen bei Erfolg den Setter setQuote() auf. Im Fehlerfall merken wir uns mit set­Error() die Fehlermeldung. Im finally-Block wiederum setzen wir den Ladezustand auf false, denn der API-Request wurde dann abgeschlossen. Der Code selbst wird in einen Aufruf von useEffect() gepackt, damit er beim ersten Rendern der Komponente durchgeführt wird.Hier der komplette Codeblock:

useEffect(() =&gt; {
  <span class="hljs-keyword">const</span> fetchQuote = <span class="hljs-keyword">async</span> () =&gt; {
    <span class="hljs-keyword">try</span> {
      <span class="hljs-keyword">const</span> response = <span class="hljs-keyword">await</span> fetch(
        <span class="hljs-string">'https://dummyjson.com/quotes/random'</span>);
      <span class="hljs-keyword">const</span> result = <span class="hljs-keyword">await</span> response.json();
      setQuote(result);
    } <span class="hljs-keyword">catch</span> (err) {
      setError(err.message);
    } <span class="hljs-keyword">finally</span> {
      setLoading(<span class="hljs-keyword">false</span>);
    }
  };
  fetchQuote();
}, []); 
Für die Ausgabe gibt es drei Fälle: Wird der API-Aufruf noch ausgeführt, wird ein Lade-Spinner (oder bei uns, aus Gründen der Einfachheit, ein entsprechender Text) ausgegeben. Im Fehlerfall erscheint eine Fehlermeldung. Hat hingegen ­alles geklappt, geben wir das vom API gelieferte Zitat aus:

<span class="hljs-keyword">if</span> (loading) <span class="hljs-keyword">return</span> <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>Loading...<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span></span>;
<span class="hljs-keyword">if</span> (error) <span class="hljs-keyword">return</span> <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>Error: {error}<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span></span>;
<span class="hljs-keyword">return</span> (
  <span class="xml"><span class="hljs-tag">&lt;&gt;</span></span>
<span class="xml">    <span class="hljs-tag">&lt;<span class="hljs-name">blockquote</span>&gt;</span>{ quote.quote } ({quote.author})</span>
<span class="xml">      <span class="hljs-tag">&lt;/<span class="hljs-name">blockquote</span>&gt;</span></span>
<span class="xml">  <span class="hljs-tag">&lt;/&gt;</span></span>
<span class="xml">);</span> 
Bild 4 zeigt das Ergebnis. Das Beispiel funktioniert ziemlich gut, umfasst aber doch eine ganze Menge Code, so um die 30 Zeilen plus Leerzeilen für die Komponente allein.
Inspirierend oder verstörend: Das zufällige Zitat des API erscheint in der Anwendung (Bild 4) © Autor
Mit dem neuen API use() geht das etwas einfacher und ­intuitiver, wobei eine solche Betrachtung natürlich immer subjektiv ist: Wir verzichten zunächst auf useState() und schreiben die Hilfsfunktion fetchQuote(), die mit dem API kommuniziert, ein wenig um, sodass sie ein Promise zurückliefert beziehungsweise eine Exception wirft:

<span class="hljs-keyword">const</span> fetchQuote = <span class="hljs-keyword">async</span> () =&gt; {
  <span class="hljs-keyword">try</span> {
    <span class="hljs-keyword">const</span> response = <span class="hljs-keyword">await</span> fetch(
      <span class="hljs-string">'https://dummyjson.com/quotes/random'</span>);
    <span class="hljs-keyword">return</span> response.json();
  } <span class="hljs-keyword">catch</span> (err) {
    <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> Error(err.message);
  }
}; 
In der Komponente selbst – diesmal heißt sie UseApp – hat use() seinen großen Auftritt. Sie erinnern sich an die Beschreibung von oben: Wir erhalten damit Zugriff auf ein Promise. Damit reduziert sich der Code zu Aufruf und Ausgabe des API auf Folgendes:

<span class="hljs-keyword">const</span> UseApp = <span class="hljs-function"><span class="hljs-params">()</span> =&gt;</span> {
  <span class="hljs-keyword">const</span> quote = use(fetchQuote());
  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">blockquote</span>&gt;</span>{ quote.quote } ({quote.author})</span>
<span class="xml">      <span class="hljs-tag">&lt;/<span class="hljs-name">blockquote</span>&gt;</span></span>
  );
} 
Gut, das mit dem Ladezustand ist hierbei unter den Tisch gefallen. Aber Sie ahnen sicherlich bereits, dass wir das deklarativ lösen können, denn in React 16 wurden nicht nur Hooks, sondern auch Suspense eingeführt. Damit ist es sehr einfach, während der Ausführung eines HTTP-Requests den Ladezustand anzuzeigen. Folgendes packen wir in die Seite, die die React-Komponente verwendet:

<span class="hljs-keyword">import</span> UseApp <span class="hljs-keyword">from</span> <span class="hljs-string">"@/components/UseApp"</span>;
<span class="hljs-keyword">import</span> { Suspense } <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>;
<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Home</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">h1</span>&gt;</span>UseApp<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span></span>
    &lt;Suspense fallback={&lt;div&gt;Loading...&lt;<span class="hljs-regexp">/div&gt;}&gt;</span>
<span class="hljs-regexp">      &lt;UseApp /</span>&gt;
    <span class="xml"><span class="hljs-tag">&lt;/<span class="hljs-name">Suspense</span>&gt;</span></span>
  );
} 
Insgesamt ein paar Zeilen weniger, und viel wichtiger: wenig Extra-Code außenherum. Die Daten aus dem API landen direkt in einer Variablen, ganz ohne useState(); die alternative Anzeige während des Ladens kommt aus React beziehungsweise Suspense. Hier werden also zwei Hooks ersetzt – zumindest in diesem Use Case. Da use() auch mit einem Kontext funktioniert, gibt es jetzt auch viele Szenarien, in denen der Einsatz von useContext() obsolet werden könnte.

Formulardaten

Das Handling von Formulardaten war in React schon immer bequem möglich, erforderte aber doch etwas zusätzlichen Code – Sie sehen hier ein Muster. Ein typisches Beispiel waren kontrollierte Formularfelder. Diese haben einen Zustand, also bedarf es eines Zustands. Hier ein typisches Beispiel:

const [email, setEmail] = useState(<span class="hljs-string">''</span>); 
// ...
<span class="hljs-keyword">return</span> (
  &lt;input
    type=<span class="hljs-string">"email"</span>
    <span class="hljs-keyword">value</span>={email} 
    onChange={e =&gt; setEmail(e.target.value)} 
  /&gt;
); 
Das nahm bei umfangreicheren Formularen gelegentlich auch mal überhand. Doch je nach Einsatzszenario geht es möglicherweise etwas einfacher. Mit „Actions“ bezeichnet der React-Kosmos Funktionen, die asynchrone Übergänge (Transitions) durchführen können. Bei Formularen etwa bekommen diese Actions dann die Informationen aus den einzelnen Feldern. In React 19 gibt es unter anderem einen neuen Hook namens useActionState(). Wenn Sie irgendwo etwas von useFormState() lesen: So hieß der Hook in der Vorabphase; der finale Release von React 19 unterstützt das nicht mehr.Doch zurück zu useActionState(): Dieser Hook kann dazu eingesetzt werden, Daten aus dem Formular entgegenzunehmen und darauf basierend etwaige Verarbeitungsergebnisse zu erhalten. Sehen wir uns das am besten wieder an einem kleinen Beispiel an. Die folgende Anweisung verwendet den Hook, um Daten aus einem Formular zu erhalten (formAc­tion) und im Nachgang eine Art Ergebnis (result). Ebenfalls könnten wir Zugriff auf den Verarbeitungszustand erhalten (isPending).

<span class="hljs-keyword">const</span> [result, formAction] = 
  useActionState(submitForm, <span class="hljs-literal">null</span>); 
Die formAction geben wir im action-Attribut des Formulars an. Hier ein maximal simples Beispiel, in dem ein Login allein nach Angabe einer E-Mail-Adresse erfolgt (don’t do this at work!).

<span class="hljs-tag">&lt;<span class="hljs-name">form</span> <span class="hljs-attr">action</span>=<span class="hljs-string">{</span> <span class="hljs-attr">formAction</span> }&gt;</span>
  Login:
  <span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"text"</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"email"</span> /&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">button</span>&gt;</span>Login<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">form</span>&gt;</span> 
Der erste Parameter von useActionState() hieß submitForm und wurde von uns bisher nicht deklariert. Wir benötigen hier eine Funktion, die zwei Informationen erhält: den vorherigen Formularzustand sowie die aktuellen Formulardaten:

<span class="hljs-keyword">const</span> submitForm = <span class="hljs-function">(<span class="hljs-params">previousState, formData</span>) =&gt;</span> {
  <span class="hljs-comment">// ...</span>
<span class="hljs-comment">}</span> 
Der Wert formData unterstützt die Methode get() zum ein­fachen Zugriff auf die Daten in einem benannten Formularfeld; wir können also beispielsweise die angegebene E-Mail-Adresse auslesen:

<span class="hljs-keyword">const</span> email = formData.<span class="hljs-built_in">get</span>(<span class="hljs-string">'email'</span>); 
Mit dieser Adresse führen wir einen simple (und unsichere) Authentifizierung durch: Alles, was auf @dotnetpro.de endet, ist gültig.Das Ergebnis der Überprüfung geben wir als Objekt zurück – die Struktur ist von uns beliebig gewählt:

<span class="hljs-keyword">if</span> (email.endsWith(<span class="hljs-string">'@dotnetpro.de'</span>)) {
  <span class="hljs-keyword">return</span> {
    <span class="hljs-string">authorized:</span> <span class="hljs-literal">true</span>,
    <span class="hljs-string">message:</span> <span class="hljs-string">"Hello dotnetpro!"</span>
  };
} <span class="hljs-keyword">else</span> {
  <span class="hljs-keyword">return</span> {
    <span class="hljs-string">authorized:</span> <span class="hljs-literal">false</span>,
    <span class="hljs-string">message:</span> <span class="hljs-string">"An error has occurred"</span>
  };
} 
Der Clou: Der Code befindet sich innerhalb der aktuellen Komponente, insbesondere ist die Rückgabe – zur Erinnerung: result nach dem Aufruf von useActionState() – für uns sichtbar. Wenn der Wert gefüllt ist, können wir ihn auch ausgeben (Bild 5):
Billigschloss: Auch mit nicht existierenden E-Mail-Adressen erlangen wir Zugriff (Bild 5) © Autor

{ result &amp;&amp; result<span class="hljs-selector-class">.authorized</span> &amp;&amp; &lt;p&gt;{ result<span class="hljs-selector-class">.message</span> }
  &lt;/p&gt; } 
Ähnlich wie zuvor den Ladestatus bei API-Aufrufen können wir mit React 19 besonders bequem den Übermittlungsstatus eines Formulars abfragen. Hierfür gibt es den neuen Hook useFormStatus().Dies soll ebenfalls ein kleines Beispiel veranschaulichen: Ein Formular besteht nur aus einer Schaltfläche. Hier das Markup der Komponente:

<span class="hljs-keyword">const</span> FormStatusApp = <span class="hljs-function"><span class="hljs-params">()</span> =&gt;</span> {
  <span class="hljs-keyword">return</span> (
  <span class="xml"><span class="hljs-tag">&lt;&gt;</span></span>
<span class="xml">    <span class="hljs-tag">&lt;<span class="hljs-name">h1</span>&gt;</span>FormStatusApp<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span></span>
<span class="xml">    <span class="hljs-tag">&lt;<span class="hljs-name">form</span> <span class="hljs-attr">action</span>=<span class="hljs-string">{</span> <span class="hljs-attr">processFormAction</span> }&gt;</span></span>
<span class="xml">      <span class="hljs-tag">&lt;<span class="hljs-name">SubmitButton</span> /&gt;</span></span>
<span class="xml">    <span class="hljs-tag">&lt;/<span class="hljs-name">form</span>&gt;</span></span>
<span class="xml">  <span class="hljs-tag">&lt;/&gt;</span>);</span>
<span class="xml">}</span> 
Auch hier wird beim Versand wieder eine Action-Funktion aufgerufen. In dieser sorgen wir dafür, dass die Verarbeitung der Formulardaten etwas länger dauert – wir bauen eine künstliche Verzögerung ein:

<span class="hljs-keyword">const</span> processFormAction = <span class="hljs-keyword">async</span> () =&gt; {
  <span class="hljs-keyword">await</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Promise</span>(<span class="hljs-function">(<span class="hljs-params">resolve</span>) =&gt;</span> 
    setTimeout(resolve, <span class="hljs-number">2500</span>));
} 
Der eigentliche Clou ist die Schaltfläche, denn diese setzt auf useFormStatus(). Dieser Hook liefert den booleschen Wert pending, der angibt, ob die Formular-Action bereits beendet ist oder nicht.Der folgende Code macht die Schaltfläche inaktiv, während die Verarbeitung noch läuft, und ändert auch den Text des Buttons:

<span class="hljs-keyword">import</span> { useFormStatus } <span class="hljs-keyword">from</span> <span class="hljs-string">"react-dom"</span>;
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">SubmitButton</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> { pending } = useFormStatus();
  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">disabled</span>=<span class="hljs-string">{</span> <span class="hljs-attr">pending</span> }&gt;</span></span>
<span class="xml">        { pending ? 'Please wait ...' : 'Submit' }</span>
<span class="xml">    <span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span></span>
  );
} 
Bild 6 zeigt die Anwendung nach einem Klick auf die Schaltfläche.
Pausenunterhaltung: Während der Datenverarbeitung erscheint ein Hinweistext (Bild 6) © Autor
Ein weiteres neues Feature in React 19 lässt sich auch, aber nicht nur beim Formularversand einsetzen. Erneut geht es darum, dass die Verarbeitung mal wieder etwas länger dauert. Wir haben bereits gesehen, wie hier Interimswerte angezeigt werden können, etwa etwas in Richtung „Bitte warten“. Bei manchen Aktionen ist aber oft schon von vornherein klar, was im Erfolgsfall („Happy Path“) das Ergebnis ist. Etwa beim Klassiker To-do-Liste: Wenn ein neuer Listeneintrag angegeben (und dann beispielsweise an das zugehörige API geschickt) wird, ist in fast allen Fällen das Ergebnis, dass das neue To-do danach auch angezeigt wird. Im optimistischen Fall kennen wir also das Ergebnis. Insofern ist es in zahlreichen Szenarien eine gute Idee, dieses wahrscheinliche Resultat auch gleich anzuzeigen. Geht trotzdem etwas schief, kann im Nachgang immer noch eine Fehlermeldung erscheinen.Für diesen Fall gibt es ab React 19 den Hook useOptimistic(). Die Funktion erwartet als Parameter einen Zustand sowie eine Funktion, die den aktuellen Zustand sowie den „optimistischen“ Wert verarbeitet und daraus einen Zustand macht, der während der Verarbeitung angezeigt wird. Hier ein Beispiel, wieder mit Zitaten, nur dass wir diese dieses Mal selbst anlegen:

const [optimisticQuote, setOptimisticQuote] = 
    useOptimistic(
  quote,
  (state, <span class="hljs-keyword">new</span><span class="hljs-type">Quote</span>) =&gt; { <span class="hljs-keyword">return</span> { text:<span class="hljs-type"></span> <span class="hljs-keyword">new</span><span class="hljs-type">Quote</span>, 
    processing:<span class="hljs-type"></span> <span class="hljs-literal">true</span> } }
); 
In der Ausgabe wird dann bei einem Formularversand der optimistische Wert sofort angezeigt. Während der Verarbeitung (processing ist dann true) zeigen wir noch einen Extratext an, der nach Ende des Prozesses wieder verschwindet:

<span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>{ optimisticQuote.text } { optimisticQuote.processing 
  &amp;&amp; <span class="hljs-tag">&lt;<span class="hljs-name">i</span>&gt;</span>(processing...)<span class="hljs-tag">&lt;/<span class="hljs-name">i</span>&gt;</span> }<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span> 
Das gesamte Beispiel ist etwas länger; Listing 1 enthält den kompletten Code der Komponente, und Listing 2 zeigt die Seite, die diese einsetzt (nebst erneut künstlich gebremster Verarbeitung). In Bild 7 sehen Sie die Ausgabe.
Listing 1: Eine Komponente mit useOptimistic()
&lt;span class="hljs-keyword"&gt;import&lt;/span&gt; { useOptimistic, useRef } &lt;span class="hljs-keyword"&gt;from&lt;/span&gt; &lt;span class="hljs-string"&gt;"react"&lt;/span&gt;;&lt;br/&gt;&lt;span class="hljs-keyword"&gt;const&lt;/span&gt; OptimisticApp = &lt;span class="hljs-function"&gt;(&lt;span class="hljs-params"&gt;{ quote, updateQuote }&lt;/span&gt;) =&amp;gt;&lt;/span&gt; {&lt;br/&gt;  &lt;span class="hljs-keyword"&gt;const&lt;/span&gt; formRef = useRef();&lt;br/&gt;  &lt;span class="hljs-keyword"&gt;const&lt;/span&gt; [optimisticQuote, setOptimisticQuote] = &lt;br/&gt;      useOptimistic(&lt;br/&gt;    quote,&lt;br/&gt;    (state, newQuote) =&amp;gt; { &lt;span class="hljs-keyword"&gt;return&lt;/span&gt; { &lt;span class="hljs-attr"&gt;text&lt;/span&gt;: newQuote, &lt;br/&gt;      &lt;span class="hljs-attr"&gt;processing&lt;/span&gt;: &lt;span class="hljs-literal"&gt;true&lt;/span&gt; } }&lt;br/&gt;  );&lt;br/&gt;  &lt;span class="hljs-keyword"&gt;async&lt;/span&gt; &lt;span class="hljs-function"&gt;&lt;span class="hljs-keyword"&gt;function&lt;/span&gt; &lt;span class="hljs-title"&gt;formAction&lt;/span&gt;(&lt;span class="hljs-params"&gt;formData&lt;/span&gt;) &lt;/span&gt;{&lt;br/&gt;    setOptimisticQuote(formData.get(&lt;span class="hljs-string"&gt;"newQuote"&lt;/span&gt;));&lt;br/&gt;    formRef.current.reset();&lt;br/&gt;    &lt;span class="hljs-keyword"&gt;await&lt;/span&gt; updateQuote(formData);&lt;br/&gt;  }&lt;br/&gt;  &lt;span class="hljs-keyword"&gt;return&lt;/span&gt; (&lt;br/&gt;    &lt;span class="xml"&gt;&lt;span class="hljs-tag"&gt;&amp;lt;&lt;span class="hljs-name"&gt;div&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="xml"&gt;      &lt;span class="hljs-tag"&gt;&amp;lt;&lt;span class="hljs-name"&gt;main&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="xml"&gt;        &lt;span class="hljs-tag"&gt;&amp;lt;&lt;span class="hljs-name"&gt;h1&lt;/span&gt;&amp;gt;&lt;/span&gt;OptimisticApp&lt;span class="hljs-tag"&gt;&amp;lt;/&lt;span class="hljs-name"&gt;h1&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="xml"&gt;        &lt;span class="hljs-tag"&gt;&amp;lt;&lt;span class="hljs-name"&gt;p&lt;/span&gt;&amp;gt;&lt;/span&gt;{ optimisticQuote.text } { &lt;/span&gt;&lt;br/&gt;&lt;span class="xml"&gt;          optimisticQuote.processing &amp;amp;&amp;amp; &lt;/span&gt;&lt;br/&gt;&lt;span class="xml"&gt;          &lt;span class="hljs-tag"&gt;&amp;lt;&lt;span class="hljs-name"&gt;i&lt;/span&gt;&amp;gt;&lt;/span&gt;(processing...)&lt;span class="hljs-tag"&gt;&amp;lt;/&lt;span class="hljs-name"&gt;i&lt;/span&gt;&amp;gt;&lt;/span&gt; }&lt;span class="hljs-tag"&gt;&amp;lt;/&lt;span class="hljs-name"&gt;p&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="xml"&gt;        &lt;span class="hljs-tag"&gt;&amp;lt;&lt;span class="hljs-name"&gt;form&lt;/span&gt; &lt;span class="hljs-attr"&gt;action&lt;/span&gt;=&lt;span class="hljs-string"&gt;{formAction}&lt;/span&gt; &lt;span class="hljs-attr"&gt;ref&lt;/span&gt;=&lt;span class="hljs-string"&gt;{formRef}&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="xml"&gt;          &lt;span class="hljs-tag"&gt;&amp;lt;&lt;span class="hljs-name"&gt;input&lt;/span&gt; &lt;span class="hljs-attr"&gt;type&lt;/span&gt;=&lt;span class="hljs-string"&gt;"text"&lt;/span&gt; &lt;span class="hljs-attr"&gt;name&lt;/span&gt;=&lt;span class="hljs-string"&gt;"newQuote"&lt;/span&gt; &lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="xml"&gt;&lt;span class="hljs-tag"&gt;            &lt;span class="hljs-attr"&gt;placeholder&lt;/span&gt;=&lt;span class="hljs-string"&gt;"Enter quote"&lt;/span&gt; /&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="xml"&gt;          &lt;span class="hljs-tag"&gt;&amp;lt;&lt;span class="hljs-name"&gt;button&lt;/span&gt; &lt;span class="hljs-attr"&gt;type&lt;/span&gt;=&lt;span class="hljs-string"&gt;"submit"&lt;/span&gt;&amp;gt;&lt;/span&gt;Update quote&lt;span class="hljs-tag"&gt;&amp;lt;/&lt;span class="hljs-name"&gt;button&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="xml"&gt;        &lt;span class="hljs-tag"&gt;&amp;lt;/&lt;span class="hljs-name"&gt;form&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="xml"&gt;    &lt;span class="hljs-tag"&gt;&amp;lt;/&lt;span class="hljs-name"&gt;main&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="xml"&gt;  &lt;span class="hljs-tag"&gt;&amp;lt;/&lt;span class="hljs-name"&gt;div&lt;/span&gt;&amp;gt;&lt;/span&gt;);&lt;/span&gt;&lt;br/&gt;&lt;span class="xml"&gt;}&lt;/span&gt;&lt;br/&gt;&lt;span class="xml"&gt;export default OptimisticApp; &lt;/span&gt; 
Daumendrücken: Das optimistische Ergebnis der Verarbeitung erscheint sofort (Bild 7) © Autor
Listing 2: Die Komponente mit useOptimistic() verwenden
&lt;span class="hljs-keyword"&gt;import&lt;/span&gt; OptimisticApp &lt;span class="hljs-keyword"&gt;from&lt;/span&gt; &lt;br/&gt;  &lt;span class="hljs-string"&gt;"@/components/OptimisticApp"&lt;/span&gt;;&lt;br/&gt;&lt;span class="hljs-keyword"&gt;import&lt;/span&gt; { useState } &lt;span class="hljs-keyword"&gt;from&lt;/span&gt; &lt;span class="hljs-string"&gt;"react"&lt;/span&gt;;&lt;br/&gt;&lt;span class="hljs-keyword"&gt;export&lt;/span&gt; &lt;span class="hljs-keyword"&gt;default&lt;/span&gt; &lt;span class="hljs-function"&gt;&lt;span class="hljs-keyword"&gt;function&lt;/span&gt; &lt;span class="hljs-title"&gt;Home&lt;/span&gt;(&lt;span class="hljs-params"&gt;&lt;/span&gt;) &lt;/span&gt;{&lt;br/&gt;  &lt;span class="hljs-keyword"&gt;const&lt;/span&gt; [ quote, setQuote ] = useState(&lt;br/&gt;    { &lt;span class="hljs-attr"&gt;text&lt;/span&gt;: &lt;span class="hljs-string"&gt;"L'État, c'est moi"&lt;/span&gt;, &lt;span class="hljs-attr"&gt;processing&lt;/span&gt;: &lt;span class="hljs-literal"&gt;false&lt;/span&gt;}&lt;br/&gt;  );&lt;br/&gt;  &lt;span class="hljs-keyword"&gt;async&lt;/span&gt; &lt;span class="hljs-function"&gt;&lt;span class="hljs-keyword"&gt;function&lt;/span&gt; &lt;span class="hljs-title"&gt;updateQuote&lt;/span&gt;(&lt;span class="hljs-params"&gt;formData&lt;/span&gt;) &lt;/span&gt;{&lt;br/&gt;    &lt;span class="hljs-comment"&gt;// künstliche Pause&lt;/span&gt;&lt;br/&gt;&lt;span class="hljs-comment"&gt;    await new Promise((res) =&amp;gt; setTimeout(res, 2500));&lt;/span&gt;&lt;br/&gt;&lt;span class="hljs-comment"&gt;    setQuote({ text: formData.get("newQuote") });&lt;/span&gt;&lt;br/&gt;&lt;span class="hljs-comment"&gt;  }&lt;/span&gt;&lt;br/&gt;&lt;span class="hljs-comment"&gt;  return &amp;lt;OptimisticApp quote={ quote } &lt;/span&gt;&lt;br/&gt;&lt;span class="hljs-comment"&gt;    updateQuote={ updateQuote } /&amp;gt;;&lt;/span&gt; 

Futter für

Eines meiner Lieblingsfeatures in React 19 steht etwas abseits des Rampenlichts, ist aber schlichtweg praktisch. Es geht um HTML-Elemente, die sich normalerweise im <head>-Abschnitt eines HTML-Dokuments befinden:
  • <link>
  • <meta>
  • <title>
Der Einsatz innerhalb einer React-Komponente war bisher eher schwierig, insbesondere bei serverseitigem Rendering. Als Workaround wurde in der Vergangenheit häufig die Komponente react-helmet [8] eingesetzt, aber deren letztes Up­date stammt aus dem Jahr 2020.Ab React 19 gibt es eine eingebaute Alternative: Die drei zuvor aufgeführten HTML-Elemente werden automatisch nach „oben“ verschoben, also innerhalb von <head>. Hier ein kleines Beispiel: Eine React-Komponente gibt die Uhrzeit ihres letzten Renderns aus. Dies implementieren wir manuell, indem wir beim Klicken auf eine Schaltfläche die aktuelle Uhrzeit in eine Statusvariable schreiben. Diese wird dann sowohl an ein <p>-Element als auch in <title> geschrieben. Letzteres Element wandert dann in den <head>-Abschnitt, sodass die Uhrzeit in der Titelleiste des Browsers sichtbar ist. Achtung: Wenn Sie ein paralleles Framework verwenden, das den <title> setzt – etwa Next.js mit exportiertem Wert metadata in der Datei layout.js –, stehen die Änderungen miteinander im Konflikt. Bild 8 zeigt das gewünschte Ergebnis.
Zeiterfassung: Ein Klick aktualisiert die Uhrzeit – auch oben in der Titelleiste (Bild 8) © Autor

import { useState, useEffect } from 'react';
const MetadataApp = () =&gt; {
  const [time, setTime] = useState('');
  useEffect(() =&gt; 
    setTime(new Date().toLocaleTimeString()), []);
  function updateTime() {
    setTime(new Date().toLocaleTimeString());
  }
  return (
  &lt;&gt;
    &lt;title&gt;{`Last rendered: ${ time }`}&lt;/title&gt;
    &lt;p&gt;Last rendered: { time }&lt;/p&gt;
    &lt;button onClick={ updateTime }&gt;Render&lt;/button&gt;
  &lt;/&gt;);
}
export default MetadataApp;  
Apropos <head>-Abschnitt: Dort gibt es in modernen Browsern einige Möglichkeiten zur Performance-Verbesserung, etwa die, dass bereits vorab Ressourcen geladen oder zumindest vorab die DNS-Auflösung durchgeführt wird. Hier ein Beispiel:

&lt;link rel="prefetch-dns" 
  href="https://www.dotnetpro.de"&gt; 
In React 19 neu eingeführte APIs ermöglichen es jetzt, solche Tags per Funktion zu erzeugen. Die entsprechenden HTML-Elemente werden generiert und in den <head>-Abschnitt eingefügt. Hier der entsprechende Aufruf für obiges <link>-Element:

prefetchDNS('https://www.dotnetpro.de'); 
Tabelle 1 enthält eine komplette Auflistung dieser neuen APIs.

Tabelle 1: APIs für spezielle Tags

API Tag
preconnect()
prefetchDNS()
preinit()

Weitere Neuerungen

React 19 bietet viele weitere Neuerungen. Hier eine – erneut subjektive – Auswahl:
  • forwardRef ist in den meisten Fällen nicht mehr notwendig; ref kann als Eigenschaft übergeben werden.
  • Apropos refs: Bei ref-Callbacks können jetzt auch „Cleanup-Funktionen“ angegeben werden, die beim Entfernen einer Komponente ausgeführt werden, beispielsweise um Ressourcen freizugeben.
  • Der Hook useTransition() unterstützt jetzt auch asynchrone Funktionen.
  • Die Fehlermeldungen – ohnehin eine der Stärken von ­React, weil in vielen Fällen sehr aussagekräftig – wurden nochmals verbessert, unter anderem bei Hydration-Fehlern.
  • Die neuen APIs prerender() und prerenderToNodeStream() erlauben die Generierung von statischen Sites.
  • Custom Elements, ein zentraler Bestandteil von Web Components, können jetzt vollständig in React verwendet werden. Das ging vorher schon, es gab aber in einigen Szenarien Schwierigkeiten, die in React 19 ausgebügelt worden sind.
Auch unter der Haube hat sich ziemlich viel getan. Besonders interessant sind möglicherweise die Arbeiten an einem neuen, noch performanteren Compiler für den Build-Prozess. Dieser verwendet Memoization, wodurch bisher notwendige Performance-Optimierungen (vor allem der Hook useMemo()) obsolet gemacht werden können. Die Idee ist, dass der Compiler die Stellen, an denen Memoization etwas bringt, automatisch erkennt, sodass das nicht mehr manuell in den Code gepackt werden muss. Die Dokumentation [9] zu dem neuen Compiler ist jedoch ziemlich ehrlich (Bild 9): Der Compiler ist eine Beta, und es sei noch einiges zu tun. Es bestehe keine Eile, ihn einzusetzen. Gegen ein Experiment spricht natürlich nichts!
Die Antwort lautet wohl Nein: Die React-Doku zum Einsatz des neuen Compilers (Bild 9) © Autor
Das, was wir in diesem Artikel behandelt haben, ist natürlich keine abschließende Liste, sondern ein bewusst ausgewähltes „Best of“. Das Release-Announcement von React 19 [5] enthält alle Details, und ein weiteres Dokument [10] hilft bei der Migration von bestehenden Codebasen auf die neueste Version. In Anbetracht der bisherigen Releasezyklen wird bis React 20 noch etwas Zeit vergehen. Ein Umstieg zum jetzigen Zeitpunkt auf React 19 kann sich lohnen.

CSR oder SSR?

In der React-Welt tobt seit einiger Zeit eine Diskussion, die auch in anderen SPA-Welten relevant ist – beispielsweise im Blazor-Kosmos. Wo wird gerendert, im Client oder auf dem Server? Je nach Präferenz spricht man von CSR (Client-Side Rendering) oder SSR (Server-Side Rendering).Für serverseitiges Rendering setzt React auf Server Components, ein ebenfalls bereits in Version 16 (zumindest konzeptionell) eingeführtes Feature. Diese Art von Komponente wird vor dem Bundling gerendert, also entweder als Teil eines Server-Build-Prozesses oder auch lokal. Neue Server Components hat das React-Team meist in einem Vorab-Kanal („Canary“) veröffentlicht. Mit dem Release von React 19 sind diese in die Hauptdistribution verlagert worden.Eigene Komponenten können mit use server für SSR und mit use client für CSR konfiguriert werden. Ein wichtiger Punkt: JavaScript-Code innerhalb von Server-Komponenten läuft dann natürlich auch nur dort.Die Zukunft wird zeigen, wie gut dieses Feature akzeptiert und adaptiert wird. Auch bei Blazor ist der Ausgang der Diskussionen weiterhin offen.

Fussnoten

  1. [1] Homepage von React, https://react.dev
  2. [2] Google Trends zu ausgewählten SPA-Frameworks, http://www.dotnetpro.de/SL2506-07React1
  3. [3] Stack Overflow Trends zu ausgewählten SPA-Frameworks, http://www.dotnetpro.de/SL2506-07React2
  4. [4] React-Releases, https://react.dev/versions
  5. [5] Veröffentlichungsmeldung zu React 19, http://www.dotnetpro.de/SL2506-07React3
  6. [6] Welche Versionen React auf welche Weise unterstützt, http://www.dotnetpro.de/SL2506-07React4
  7. [7] Öffentliches Zitate-API, http://www.dotnetpro.de/SL2506-07React5
  8. [8] react-helmet auf GitHub, http://www.dotnetpro.de/SL2506-07React6
  9. [9] Der neue React-Compiler, http://www.dotnetpro.de/SL2506-07React7
  10. Migration zu React 19, http://www.dotnetpro.de/SL2506-07React8

Neueste Beiträge

DWX hakt nach: Wie stellt man Daten besonders lesbar dar?
Dass das Design von Websites maßgeblich für die Lesbarkeit der Inhalte verantwortlich ist, ist klar. Das gleiche gilt aber auch für die Aufbereitung von Daten für Berichte. Worauf besonders zu achten ist, erklären Dr. Ina Humpert und Dr. Julia Norget.
3 Minuten
27. Jun 2025
DWX hakt nach: Wie gestaltet man intuitive User Experiences?
DWX hakt nach: Wie gestaltet man intuitive User Experiences? Intuitive Bedienbarkeit klingt gut – doch wie gelingt sie in der Praxis? UX-Expertin Vicky Pirker verrät auf der Developer Week, worauf es wirklich ankommt. Hier gibt sie vorab einen Einblick in ihre Session.
4 Minuten
27. Jun 2025
„Sieh die KI als Juniorentwickler“
CTO Christian Weyer fühlt sich jung wie schon lange nicht mehr. Woran das liegt und warum er keine Angst um seinen Job hat, erzählt er im dotnetpro-Interview.
15 Minuten
27. Jun 2025
Miscellaneous

Das könnte Dich auch interessieren

UIs für Linux - Bedienoberflächen entwickeln mithilfe von C#, .NET und Avalonia
Es gibt viele UI-Frameworks für .NET, doch nur sehr wenige davon unterstützen Linux. Avalonia schafft als etabliertes Open-Source-Projekt Abhilfe.
16 Minuten
16. Jun 2025
Mythos Motivation - Teamentwicklung
Entwickler bringen Arbeitsfreude und Engagement meist schon von Haus aus mit. Diesen inneren Antrieb zu erhalten sollte für Führungskräfte im Fokus stehen.
13 Minuten
19. Jan 2017
Evolutionäres Prototyping von Business-Apps - Low Code/No Code und KI mit Power Apps
Microsoft baut Power Apps zunehmend mit Features aus, um die Low-Code-/No-Code-Welt mit der KI und der professionellen Programmierung zu verbinden.
19 Minuten
Anzeige
Anzeige
Anzeige
Anzeige
Anzeige