React 19.3
9 settembre 2026 del React Team
React 19.3 è ora disponibile su npm!
Lo scorso anno abbiamo presentato View Transitions e Fragment Refs come nuove API sperimentali in arrivo in React. Siamo entusiasti di annunciare che entrambe sono ora stabili in React 19.3!
In questo post vedremo come funzionano e tratteremo anche altre novità rilevanti di questa release.
- Nuove funzionalità di React
- Nuove funzionalità di React DOM
- Nuove funzionalità di React Server Components
- Changelog
Nuove funzionalità di React
View Transitions
Il nuovo componente <ViewTransition> ti permette di animare elementi quando entrano, escono, si spostano o cambiano dimensione usando la View Transition API del browser. L’abbiamo condivisa come API sperimentale lo scorso anno, e in 19.3 è stabile e pronta all’uso.
Per animare una parte della tua UI, avvolgila in <ViewTransition>:
import { ViewTransition } from 'react';
{isShowing && (
<ViewTransition>
<Component />
</ViewTransition>
)}Ora, ogni volta che un aggiornamento contrassegnato come Transition cambia lo stile del componente figlio, o causa il mount o l’unmount di ViewTransition, React animerà quell’aggiornamento.
React sceglie quale animazione eseguire in base a come è cambiato l’albero:
- enter: viene aggiunto
<ViewTransition>. - exit: viene rimosso
<ViewTransition>. - update: i figli di un
<ViewTransition>cambiano stile o contenuto. - share: un
<ViewTransition>con nome viene rimosso in un punto e aggiunto in un altro.
Nota che gli aggiornamenti non contrassegnati come Transition non attivano animazioni, perché sono pensati per essere urgenti e riflessi immediatamente nella UI. Gli aggiornamenti di state dentro startTransition, la rivelazione di un <Suspense> o un aggiornamento da useDeferredValue attivano tutti l’animazione di una View Transition.
Ecco un semplice esempio di animazione enter/exit:
import { ViewTransition, useState, startTransition } from 'react'; import { Video } from './Video'; import videos from './data'; export default function Component() { const [showItem, setShowItem] = useState(false); return ( <> <button onClick={() => { startTransition(() => { setShowItem((prev) => !prev); }); }}> {showItem ? '➖' : '➕'} </button> {showItem && ( <ViewTransition> <Video video={videos[0]} /> </ViewTransition> )} </> ); }
Per impostazione predefinita, <ViewTransition> anima con un cross-fade fluido. Puoi personalizzare ogni tipo di animazione passando una View Transition Class e definendo l’animazione in CSS, oppure puoi usare la Web Animations API per attivare animazioni in modo imperativo con le event props (onEnter, onExit, onShare, onUpdate).
Attualmente, <ViewTransition> funziona solo nel DOM. Stiamo lavorando al supporto per React Native e altre piattaforme.
Per saperne di più, consulta la documentazione di <ViewTransition>.
addTransitionType
A volte vorrai personalizzare quale animazione usare per lo stesso aggiornamento di state. Ad esempio, navigare un carosello in avanti fino alla terza slide dovrebbe animare le slide da destra a sinistra, mentre navigarlo indietro dovrebbe animarle da sinistra a destra, anche se entrambe le azioni impostano currentSlide a 3.
Puoi personalizzare l’animazione per una data View Transition chiamando addTransitionType insieme all’aggiornamento di state. Questo ti permette di aggiungere più informazioni sulla causa di una particolare Transition:
function nextSlide() {
startTransition(() => {
addTransitionType('next');
setCurrentSlide(c => c + 1);
});
}
function previousSlide() {
startTransition(() => {
addTransitionType('previous');
setCurrentSlide(c => c - 1);
});
}Poi puoi specificare animazioni diverse in base a quel Transition Type:
<ViewTransition
enter={{
'next': 'from-right',
'previous': 'from-left',
}}
exit={{
'next': 'to-left',
'previous': 'to-right',
}}
>
<Page />
</ViewTransition>Ecco un esempio:
import { ViewTransition, addTransitionType, useState, startTransition, Fragment } from 'react'; import { Video } from './Video'; import videos from './data'; import './animations.css'; export default function Component() { const [selected, setSelected] = useState(0) const video = videos[selected]; return ( <> <div className="button-container"> <button onClick={() => { startTransition(() => { addTransitionType('previous'); setSelected(c => c > 0 ? c - 1 : videos.length - 1 ) }); }}> ⬅️ </button> <button onClick={() => { startTransition(() => { addTransitionType('next'); setSelected(c => c + 1 < videos.length ? c + 1 : 0) }); }}> ➡️ </button> </div> <ViewTransition key={video.id} enter={{ 'next': 'from-right', 'previous': 'from-left' }} exit={{ 'next': 'to-left', 'previous': 'to-right' }} > <Video video={video} /> </ViewTransition> </> ); }
React aggiunge anche ogni Transition Type all’elemento come view transition type del browser, così puoi delimitare le animazioni in CSS con :active-view-transition-type(...).
Per saperne di più, consulta la documentazione di addTransitionType.
Animare fallback, immagini e font con Suspense
Una delle cose più interessanti delle View Transitions in React è come si integrano con Suspense.
Puoi animare un boundary Suspense mentre rivela i suoi figli avvolgendolo in <ViewTransition>:
<ViewTransition>
<Suspense fallback={<Loading />}>
<Component />
</Suspense>
</ViewTransition>Quando i figli finiscono di caricarsi, React attiverà un’animazione di update dal fallback al contenuto finale.
Ecco un esempio. Prova a premere ➕ per renderizzare un LazyVideo che sospende la prima volta che viene renderizzato:
import { Suspense, useState, startTransition, use, ViewTransition } from 'react'; import { Video, VideoPlaceholder } from './Video'; import { fetchVideo } from './data'; export default function Component() { const [showItem, setShowItem] = useState(false); return ( <> <button onClick={() => { startTransition(() => { setShowItem((prev) => !prev); }); }} > {showItem ? '➖' : '➕'} </button> {showItem && ( <ViewTransition> <Suspense fallback={<VideoPlaceholder />}> <LazyVideo /> </Suspense> </ViewTransition> )} </> ); } function LazyVideo() { const video = use(fetchVideo()); return <Video video={video} />; }
Anche se funziona, noterai che il video continua ad animarsi entrando e uscendo nelle rivelazioni successive, anche se è già stato caricato. (Potresti anche notare che il fallback sfuma la prima volta che viene mostrato.)
In generale, le animazioni con Suspense funzionano meglio se usate con parsimonia ed evitate per UI già in cache che altrimenti apparirebbe istantaneamente.
Ecco alcuni principi per ottenere una buona UX quando animi con Suspense:
- I fallback dovrebbero apparire immediatamente senza animazione
- Un fallback dovrebbe aggiornarsi al contenuto finale con animazione
- I figli che non sospendono dovrebbero apparire immediatamente senza animazione
Questo mantiene l’app reattiva quando i contenuti sono già caricati, e usa l’animazione solo per fluidificare il passaggio dal fallback al contenuto finale.
Per correggere l’esempio sopra, possiamo disabilitare tutte le animazioni tranne quelle di update:
<ViewTransition update="auto" default="none">
<Suspense fallback={<Fallback />}>
<Component />
</Suspense>
</ViewTransition>Vediamo come si comporta ora:
import { Suspense, useState, startTransition, use, ViewTransition } from 'react'; import { Video, VideoPlaceholder } from './Video'; import { fetchVideo } from './data'; export default function Component() { const [showItem, setShowItem] = useState(false); return ( <> <button onClick={() => { startTransition(() => { setShowItem((prev) => !prev); }); }} > {showItem ? '➖' : '➕'} </button> {showItem && ( <ViewTransition update="auto" default="none"> <Suspense fallback={<VideoPlaceholder />}> <LazyVideo /> </Suspense> </ViewTransition> )} </> ); } function LazyVideo() { const video = use(fetchVideo()); return <Video video={video} />; }
Nota come il fallback appare immediatamente quando tocchi il bottone, mantenendo la UI reattiva alle azioni dell’utente. Inoltre, una volta caricato il video, attivarlo o disattivarlo è istantaneo.
Ci sono altri pattern che puoi usare a seconda dell’effetto che vuoi ottenere. Per saperne di più, consulta la documentazione su animare con Suspense.
Oltre ad animare i fallback, le View Transitions permettono di far sì che immagini o font attivino Suspense durante il caricamento.
Questo ti permette di evitare il comportamento predefinito del browser in cui immagini o font possono comparire con un flicker non appena finiscono di caricarsi, e invece costruire sequenze di caricamento coordinate che considerano tutte le risorse di un componente.
Avvolgi immagini o font dentro <ViewTransition> per attivare Suspense mentre si caricano:
<ViewTransition>
<Suspense fallback={<Fallback />}>
<img src={imageSrc} />
<style href={fontSrc} precedence="default">
{`@font-face {
font-family: 'Fancy';
src: url(${fontSrc}) format('truetype');
font-display: swap;
}`}
</style>
</Suspense>
</ViewTransition>Ecco un esempio di componente che sospende finché dati, immagine e font non sono tutti caricati:
import { ViewTransition, Suspense, use, useState, startTransition } from 'react'; import { fetchQuote } from './data.js'; import { freshStylesheetUrl, freshImageUrl } from './resources.js'; import { ProfileCard, ProfileCardLoading } from './ProfileCard.js'; import { VanillaProfileCard } from './VanillaProfileCard.js'; export default function App() { const [resources, setResources] = useState(null); return ( <> <button onClick={() => { startTransition(() => { setResources({ quotePromise: fetchQuote(), stylesheet: freshStylesheetUrl(), image: freshImageUrl(), }); }); }}> Show profile </button> {resources && ( <ViewTransition update='auto' default='none'> <Suspense fallback={<ProfileCardLoading />}> <ProfileCard resources={resources} /> </Suspense> </ViewTransition> )} <hr /> <VanillaProfileCard /> </> ); }
Per saperne di più sull’attesa del caricamento di immagini, font o stylesheet, consulta la documentazione di Suspense.
Fragment Refs
Quando hai bisogno di un controllo più a basso livello sui nodi DOM di un componente — ad esempio per collegare un event listener, osservare la visibilità o spostare il focus — di solito puoi usare un ref. Ma ci sono situazioni in cui questo è difficile:
- Componenti che renderizzano un gruppo di sibling senza un singolo genitore
- Componenti che non passano la prop
refa un altro elemento
function Component() {
// Come possiamo lavorare con l'elenco di nodi DOM renderizzati da questo componente?
return (
{posts.map(post => (
<Heading key={post.id}>
{post.title}
</Heading>
))}
)
}Aggiungere un wrapper <div> solo per tenere un ref a volte funziona, ma può anche interferire con lo styling o il layout del componente. Inoltre, se un componente non espone una prop ref, dovresti modificarlo per farlo, il che potrebbe essere impossibile se proviene da una libreria che non controlli.
I Fragment Refs risolvono questi problemi fornendo un insieme limitato di metodi DOM comunemente usati che funzionano con qualsiasi componente React, indipendentemente da cosa renderizza.
In 19.3, puoi usarli passando un ref direttamente a un <Fragment>. Questo ref ti dà un FragmentInstance, che puoi usare per lavorare con i figli DOM del Fragment:
function Component() {
const fragmentRef = useRef(null);
useEffect(() => {
const fragmentInstance = fragmentRef.current;
fragmentInstance.focus();
}, []);
return (
<Fragment ref={fragmentRef}>
{posts.map(post => (
<Heading key={post.id}>
{post.title}
</Heading>
))}
</Fragment>
)
}Il FragmentInstance opera sul DOM dei figli come gruppo, senza cambiarne la struttura:
addEventListener,removeEventListeneredispatchEventgestiscono gli eventi per i figli di primo livello.focus,focusLasteblurspostano il focus tra i figli annidati, in profondità prima.observeUsingeunobserveUsingcollegano unIntersectionObservero unResizeObserver.getClientRects,getRootNode,compareDocumentPositionescrollIntoViewti permettono di misurare e scorrere fino ai figli di primo livello del fragment.
Quindi, i Fragment Refs ti permettono di collegare comportamenti ad altri componenti senza doverne modificare l’interno, o senza cambiare la struttura DOM che già producono.
Questo esempio mostra un componente InView con una prop onChange che scatta ogni volta che i suoi figli entrano o escono dal viewport:
import { useState } from 'react'; import Card from './Card'; import InView from './InView'; export default function App() { const [isVisible, setIsVisible] = useState(true); return ( <div className={isVisible ? 'page visible' : 'page'}> <div className="filler">Scroll down</div> <InView onChange={setIsVisible}> <Card title="First section" /> <Card title="Second section" /> </InView> <div className="filler">Scroll up</div> </div> ); }
Nota come InView riesce ad aggiungere comportamento ai suoi figli, anche se non c’è un singolo elemento DOM genitore, e nonostante Card non esponga una prop ref.
Per saperne di più sull’uso dei Fragment Refs, consulta la documentazione di <Fragment>.
Nuove funzionalità di React DOM
browser
Se la tua app usa il server rendering, i componenti renderizzeranno in due ambienti diversi:
- Sul server, i componenti renderizzano per produrre l’HTML iniziale
- Sul client, i componenti renderizzano per arricchire quell’HTML con gestori di eventi
Nella maggior parte dei casi, i componenti dovrebbero produrre HTML che corrisponde al loro output iniziale renderizzato sul client, garantendo un’idratazione corretta e permettendo agli utenti di vedere quanto più contenuto possibile al caricamento iniziale.
Ma in rari casi, un componente potrebbe non riuscire a produrre UI significativa sul server. Ad esempio, potrebbe dipendere da un’API disponibile solo nel browser come localStorage, o leggere il fuso orario locale del browser. In questi casi, potresti voler escludere del tutto quel componente dal server rendering.
In precedenza, potevi farlo usando dello state che aggiornavi in un Effetto, o controllando la presenza di API del browser come window:
function Component() {
const [mounted, setMounted] = useState(false);
useEffect(() => {
setMounted(true)
}, [])
// ...
}
function Component() {
const isBrowser = typeof window !== 'undefined';
// ...
}In 19.3, React include ora un’API di prima classe per questa tecnica.
Un componente può chiamare use(browser()) per escludersi dal server-side rendering:
import { use } from 'react';
import { browser } from 'react-dom';
function Component() {
use(browser());
// ...
}Questo attiverà Suspense sul server, ma non sul client. Durante il server-side rendering, il fallback del boundary Suspense più vicino apparirà nell’HTML. Una volta idratato il componente sul client, use(browser()) non sospende, permettendo al componente di continuare a renderizzare normalmente.
Ecco un esempio di componente che renderizza il fuso orario locale del tuo dispositivo. Premi Reload per vedere l’HTML iniziale seguito dalla prima renderizzazione di React sul client:
import { Suspense, use } from 'react'; import { browser } from 'react-dom'; function TimeZone() { use(browser()); const timeZone = new Intl.DateTimeFormat().resolvedOptions().timeZone; return <p>{timeZone}</p> } export default function App() { return ( <> <p>Your current time zone is:</p> <Suspense fallback="Loading..."> <TimeZone /> </Suspense> </> ); }
Poiché TimeZone sospende sul server, l’HTML iniziale include il fallback Suspense. Dopo un breve ritardo artificiale, React idrata la pagina, permettendo al componente di renderizzare normalmente nel browser.
Quindi, per i componenti che non possono produrre UI significativa durante il server rendering, browser ti permette di usare Suspense per i loro stati di caricamento, permettendo loro di partecipare insieme ad altri componenti che sospendono finché non sono pronti a renderizzare.
Come altre chiamate a use, use(browser()) può essere chiamato dentro un’istruzione condizionale o dopo un early return. Questo ti permette di scrivere componenti o Hook personalizzati che possono escludersi dal server rendering in base a una condizione, come il valore di una prop.
Ecco lo stesso esempio di sopra, tranne che ora il nostro componente TimeZone accetta un valore predefinito opzionale che può renderizzare come parte dell’HTML iniziale:
import { Suspense, use } from 'react'; import { browser } from 'react-dom'; function TimeZone({ defaultValue }) { if (defaultValue) { return <p>{defaultValue}</p>; } use(browser()); const localTimeZone = new Intl.DateTimeFormat().resolvedOptions().timeZone; return <p>{localTimeZone}</p> } export default function App() { return ( <> <div> <p>The event's time zone is:</p> <TimeZone defaultValue='America/New_York' /> </div> <hr /> <div> <p>Your current time zone is:</p> <Suspense fallback="Loading..."> <TimeZone /> </Suspense> </div> </> ); }
Nota come TimeZone sospende solo nel secondo caso, quando non viene fornito un valore predefinito.
Un altro esempio utile di questo pattern è escludere dal server rendering un Hook per il data fetching come useQuery, a meno che i dati iniziali della query non siano stati passati (ad esempio da un Server Component o dalla funzione loader del framework):
function useBrowserQuery(query, options) {
if (options.initialData === undefined) {
use(browser());
}
return useQuery(query, options);
}
function ProductDetails({ productId, initialData }) {
const product = useBrowserQuery(`/api/products/${productId}`, {
initialData,
});
return <h1>{product.name}</h1>;
}Ora, il componente ProductDetails può essere incluso nell’HTML, a patto che riceva initialData durante il server rendering. Altrimenti, sospende finché non viene renderizzato nel browser, momento in cui useQuery può recuperare i dati o leggerli dalla cache normalmente.
Per saperne di più su browser, consulta la documentazione.
Supporto Trusted Types
React 19.3 si integra con la Trusted Types API del browser, una funzionalità di sicurezza che aiuta a prevenire attacchi XSS basati sul DOM. Quando un sito applica Trusted Types con Content-Security-Policy: require-trusted-types-for 'script', il browser richiede che i valori passati a injection sink come innerHTML siano oggetti tipizzati (TrustedHTML, TrustedScript, TrustedScriptURL) creati tramite le tue policy di sanitizzazione, piuttosto che stringhe grezze.
In precedenza, React convertiva sempre i valori in stringhe (via '' + value) prima di passarli alle API DOM, trasformando gli oggetti Trusted Types di nuovo in stringhe semplici che il browser rifiutava. React ora passa questi valori senza coercizione, così il browser può validarli e le tue policy Trusted Types funzionano come previsto.
Nuove funzionalità di React Server Components
<Context> può essere renderizzato direttamente nei Server Components
Anche se i Server Components non possono creare Context, possono renderizzare Context importandolo da un modulo 'use client'.
In precedenza, questo richiedeva che il modulo client esportasse un componente wrapper separato, spesso chiamato Provider:
// user-context.js
'use client';
import { createContext } from 'react';
export const UserContext = createContext(null);
export function UserProvider({ currentUser, children }) {
return <UserContext value={currentUser}>{children}</UserContext>;
}// server-component.js
import { UserProvider } from './user-context';
export async function Layout({ children }) {
const currentUser = await getCurrentUser();
return (
<UserProvider currentUser={currentUser}>
{children}
</UserProvider>
)
}Nota che in questo esempio, il provider non fa altro che passare la prop dal Server Component direttamente al Context.
In React 19.3, i Server Components possono importare e renderizzare Context direttamente da un modulo 'use client', senza un componente wrapper aggiuntivo:
// user-context.js
'use client';
import { createContext } from 'react';
export const UserContext = createContext(null);// server-component.js
import { UserContext } from './user-context';
export async function Layout({ children }) {
const currentUser = await getCurrentUser();
return (
<UserContext value={currentUser}>
{children}
</UserContext>
)
}Questo è particolarmente utile per i Context che esistono solo per permettere ai Server Components di condividere dati con il resto dell’albero client.
Changelog
Altre novità rilevanti
react: Renderizza le Transition in modo indipendente invece di intrecciarle in una singola renderizzazione, così una Transition lenta non blocca più quelle non correlate #37290react-dom: Invoca due volte gli Effetti in Strict Mode durante l’idratazione, allineandosi alle root renderizzate sul client #35961react: Aggiunge un warning quandouseviene usato in modo errato in una condizionale #37104react: Rinomina “form state” in “action state” nei messaggi di errore diuseActionState#35790react-dom: Aggiunge supporto per gli eventionFullscreenChangeeonFullscreenError#34621react-dom: Aggiunge supporto per la proprietà SVGmaskType#35921react-dom: SupportafetchPriorityper le risorse module #36835react-dom: AttivaonResetquando React resetta automaticamente un form dopo una Server Action #35176react-dom: Include ilsubmitternegli eventisubmit#35590react-dom: Riconoscecredentiallesscome attributo booleano sugli iframe #36148react-dom: Raggruppa gli aggiornamenti dagli eventiresizefino al frame successivo #35117react-server: TrasportaError.cause#35810 eAggregateError.errors#36156 al clientreact-server: Aggiunge supporto per<Activity>in Flight #34697
Bug fix rilevanti
react: CorreggeuseDeferredValuebloccato su un valore obsoleto #36134react: Corregge la propagazione del context nei fallback Suspense #36160 e attraverso boundary Suspense sospesi #35839react: Corregge un blocco quando si aggiorna un boundary Suspense disidratato dentro un albero nascosto #37135react: CorreggeuseSyncExternalStoreche perdeva mutazioni dello store avvenute mentre un albero<Activity>era nascosto #36947react: CorreggeuseEffectEventper leggere gli ultimi valori nei componentiforwardRefememo#34831react: Corregge il reset dello status del form quando lo state del componente viene aggiornato #34075react: Corregge diversi bug di Fast Refresh conlazy,memoe modifiche che cambiano il tipo di un componente #36965, #36964, #36963, #36950react: Corregge un bug per cui<title>veniva ancora spostato in<head>dopo che l’<Activity>contenente<title>cambiava modalità davisibleahidden#34983react: Non lascia sfuggire errori da un<Activity>nascosto #35074react: Nasconde i contenuti del portal renderizzati dentro un<Activity>nascosto #35091react: Non fa riferimento al tipo interno<Offscreen>nei messaggi di errore #35763react-dom: Corregge il focus per elementi delegati e già focalizzati #36010react-dom: Corregge una perdita di listener diFragmentInstancenormalizzando le opzioni capture secondo la spec DOM #36047react-dom: Corregge un crash di<ViewTransition>in Mobile Safari #35337react-dom: Corregge un crash di<ViewTransition>conSuspenseList#35520react-dom: AggiornadefaultValueper inputtype="number"per allinearli ad altri tipi di input #36980react-dom: Evita di impostareinnerHTMLquando non è cambiato #36949react-dom: Corregge un falso positivo di mismatch di idratazione sugli attributinonce#37030react-dom: Corregge il blocco direact-dom/serversu Deno #35235react-server: Corregge vociFormDataperse indecodeReplyFromBusboy#36468react-server: Corregge uno stack overflow con catene async profonde #35612 e unRangeErrorda crescita esponenziale delle info di debug #37481
Per l’elenco completo delle modifiche, consulta il Changelog.
Grazie a Sam Selikoff per aver scritto questo post, e a Matt Carroll, Dan Abramov e Andrew Clark per aver revisionato questo post.