React 19 useEffectEvent e Activity: Nuove API e Domande da Colloquio 2026
Guida approfondita a useEffectEvent e al componente Activity di React 19.2 – con esempi di codice, best practice e domande frequenti nei colloqui tecnici 2026.

React 19.2 ha introdotto due API che affrontano problemi di vecchia data: useEffectEvent elimina le stale closure negli effect, mentre <Activity> consente il pre-rendering in background con preservazione completa dello state. Entrambe le API sono state rilasciate a ottobre 2025 e stanno già modificando il modo in cui le applicazioni React in produzione gestiscono side effect e navigazione.
useEffectEvent e Activity richiedono React 19.2 o versione successiva. L'aggiornamento si esegue con npm install react@latest react-dom@latest. Il plugin ESLint eslint-plugin-react-hooks@6+ aggiunge supporto nativo per useEffectEvent nei dependency array.
Il problema delle stale closure: perch\u00e9 serve useEffectEvent
Ogni sviluppatore React ha incontrato almeno una volta il problema delle stale closure. Un effect cattura un valore al momento del rendering e, quando quel valore cambia, l'effect continua a fare riferimento a quello vecchio. La soluzione classica prevedeva l'utilizzo di useRef per mantenere un riferimento mutabile: funzionale, ma verboso e invisibile al linter.
Si consideri un'applicazione di chat che registra analytics all'arrivo di un nuovo messaggio. Il log deve includere il theme corrente, ma i cambiamenti del tema non devono riconnettere la chat:
// Prima di useEffectEvent: workaround con useRef
import { useEffect, useRef } from 'react'
export function useChatRoom(roomId: string, theme: string) {
// Salvare il theme in un ref per evitare la stale closure
const themeRef = useRef(theme)
themeRef.current = theme
useEffect(() => {
const connection = createConnection(roomId)
connection.on('message', (msg) => {
// themeRef.current ha sempre il valore aggiornato
logAnalytics('new_message', { roomId, theme: themeRef.current })
showNotification(msg)
})
connection.connect()
return () => connection.disconnect()
}, [roomId]) // theme escluso intenzionalmente — ma il linter avvisa
}Questo approccio funziona, ma il linter segnala theme come dipendenza mancante. Sopprimere l'avviso nasconde potenziali bug altrove. Il pattern useRef oscura anche l'intento: chi legge il codice per la prima volta deve ricostruire mentalmente il motivo per cui theme si trova in un ref invece che nel dependency array.
useEffectEvent: separare la logica reattiva da quella non reattiva
L'hook useEffectEvent crea una funzione stabile che legge sempre le props e lo state pi\u00f9 recenti, senza attivare la risincronizzazione dell'effect. Sostituisce il pattern useRef con un'API dichiarativa che il linter comprende nativamente.
// Dopo useEffectEvent: separazione netta delle responsabilit\u00e0
import { useEffect, useEffectEvent } from 'react'
export function useChatRoom(roomId: string, theme: string) {
// Effect Event: legge sempre il theme corrente, non provoca reconnect
const onMessage = useEffectEvent((msg: string) => {
logAnalytics('new_message', { roomId, theme })
showNotification(msg)
})
useEffect(() => {
const connection = createConnection(roomId)
connection.on('message', onMessage)
connection.connect()
return () => connection.disconnect()
}, [roomId]) // Nessun avviso del linter — onMessage \u00e8 un Effect Event
}L'effect viene rieseguito solo quando roomId cambia. Il callback onMessage vede sempre il theme corrente senza essere elencato come dipendenza. La documentazione ufficiale di React stabilisce esplicitamente che gli Effect Event devono essere chiamati solo dall'interno degli effect, mai durante il rendering o passati ai componenti figli.
Regole e vincoli di useEffectEvent
Tre regole governano useEffectEvent:
- Va chiamato al livello superiore di un componente o di un custom hook, mai all'interno di loop o condizioni
- La funzione restituita pu\u00f2 essere chiamata solo all'interno di
useEffecto di un altro Effect Event - Non deve essere passata come prop n\u00e9 restituita da un hook per uso esterno
Violare la regola 2 causa bug sottili: l'identit\u00e0 della funzione cambia a ogni render, quindi salvarla in un ref o passarla verso il basso vanifica lo scopo. Il plugin eslint-plugin-react-hooks@6+ applica questi vincoli automaticamente.
// Corretto: Effect Event utilizzato all'interno di useEffect
import { useEffect, useEffectEvent, useState } from 'react'
interface SearchTrackerProps {
query: string
userId: string
}
export function SearchTracker({ query, userId }: SearchTrackerProps) {
const [results, setResults] = useState<string[]>([])
// Tracciare le ricerche con il contesto utente corrente
const onSearchComplete = useEffectEvent((resultCount: number) => {
analytics.track('search_complete', {
query,
userId, // Sempre la userId aggiornata
resultCount,
timestamp: Date.now(),
})
})
useEffect(() => {
const controller = new AbortController()
fetchSearchResults(query, controller.signal).then((data) => {
setResults(data)
onSearchComplete(data.length) // Chiamato dall'interno di useEffect
})
return () => controller.abort()
}, [query]) // userId esclusa in sicurezza — risiede nell'Effect Event
return <ResultsList results={results} />
}useEffectEvent non \u00e8 una scappatoia per silenziare il linter delle dipendenze. Se un valore controlla realmente quando un effect deve rieseguirsi, appartiene al dependency array. La logica va estratta in un Effect Event solo quando rappresenta un'azione secondaria (logging, notifiche, analytics) che legge valori reattivi senza dover riattivare l'effect.
Il componente Activity: pre-rendering in background con preservazione dello state
Il componente <Activity> (precedentemente denominato "Offscreen") controlla se i suoi figli sono visibili o nascosti. A differenza del rendering condizionale che distrugge lo state, o di CSS display: none che mantiene gli effect in esecuzione, <Activity> preserva lo state mentre esegue il cleanup degli effect e posticipa gli aggiornamenti.
// Activity preserva lo state del form durante il cambio tab
import { useState } from 'react'
import { Activity } from 'react'
interface Tab {
id: string
label: string
content: React.ReactNode
}
export function TabLayout({ tabs }: { tabs: Tab[] }) {
const [activeTab, setActiveTab] = useState(tabs[0].id)
return (
<div>
<nav className="flex gap-2 border-b border-border">
{tabs.map((tab) => (
<button
key={tab.id}
onClick={() => setActiveTab(tab.id)}
className={activeTab === tab.id ? 'border-b-2 border-primary' : ''}
>
{tab.label}
</button>
))}
</nav>
{tabs.map((tab) => (
<Activity key={tab.id} mode={activeTab === tab.id ? 'visible' : 'hidden'}>
{tab.content}
</Activity>
))}
</div>
)
}Se un utente sta compilando un form nella scheda "Profilo", pu\u00f2 passare alla scheda "Impostazioni" e tornare indietro senza perdere i dati inseriti. Gli effect della scheda nascosta (timer, subscription, fetch di dati) vengono ripuliti, liberando risorse. Quando la scheda torna visibile, gli effect vengono rimontati e lo state viene ripristinato istantaneamente.
Pronto a superare i tuoi colloqui su React / Next.js?
Pratica con i nostri simulatori interattivi, flashcards e test tecnici.
Modalit\u00e0 Activity: visible e hidden nel dettaglio
Il componente <Activity> accetta una prop mode con due valori:
| Comportamento | mode="visible" | mode="hidden" |
|----------|-------------------|------------------|
| Rendering DOM | Normale | display: none via CSS |
| State del componente | Attivo | Preservato in memoria |
| Effect (useEffect) | Montati | Ripuliti |
| Priorit\u00e0 aggiornamenti | Normale | Posticipata a idle |
| Pre-rendering | N/A | Rendering a bassa priorit\u00e0 |
Quando un componente parte nascosto (primo render con mode="hidden"), React lo renderizza a bassa priorit\u00e0 senza montare gli effect. Questo abilita la navigazione istantanea: la pagina di destinazione \u00e8 gi\u00e0 renderizzata in background quando l'utente fa clic.
// Pre-renderizzare la prossima route probabile in background
import { Activity, Suspense, use } from 'react'
interface PrerenderedRouteProps {
isActive: boolean
dataPromise: Promise<DashboardData>
}
export function PrerenderedRoute({ isActive, dataPromise }: PrerenderedRouteProps) {
return (
<Activity mode={isActive ? 'visible' : 'hidden'}>
<Suspense fallback={<DashboardSkeleton />}>
<DashboardContent dataPromise={dataPromise} />
</Suspense>
</Activity>
)
}
function DashboardContent({ dataPromise }: { dataPromise: Promise<DashboardData> }) {
// use() legge la promise in cache — funziona durante il pre-render nascosto
const data = use(dataPromise)
return (
<div className="grid grid-cols-3 gap-4">
<MetricsCard data={data.metrics} />
<ChartPanel data={data.charts} />
<RecentActivity items={data.activity} />
</div>
)
}L'<Activity> nascosta pre-renderizza la dashboard a priorit\u00e0 idle. Combinata con Suspense e l'API use, il caricamento dei dati avviene in background. Quando isActive passa a true, il contenuto appare senza alcun indicatore di caricamento.
Activity e TanStack Query: l'insidia della cache
Un errore frequente con <Activity> riguarda TanStack Query. Poich\u00e9 useQuery si basa internamente su useEffect, le query all'interno di un'<Activity> nascosta non verranno eseguite: l'effect \u00e8 smontato.
// Problema: useQuery non effettua il fetch quando Activity \u00e8 nascosta
import { useQuery, useQueryClient } from '@tanstack/react-query'
import { Activity } from 'react'
function UserStats() {
// Questa useQuery NON verr\u00e0 eseguita mentre \u00e8 nascosta
const { data } = useQuery({
queryKey: ['user-stats'],
queryFn: fetchUserStats,
})
return <StatsDisplay data={data} />
}
// Soluzione: precaricare i dati al di fuori del confine Activity
function DashboardWithPrefetch({ showStats }: { showStats: boolean }) {
const queryClient = useQueryClient()
// Prefetch al livello genitore — eseguito indipendentemente dalla modalit\u00e0 Activity
queryClient.ensureQueryData({
queryKey: ['user-stats'],
queryFn: fetchUserStats,
})
return (
<Activity mode={showStats ? 'visible' : 'hidden'}>
<UserStats />
</Activity>
)
}La soluzione \u00e8 diretta: spostare il prefetch dei dati in un componente genitore o utilizzare queryClient.ensureQueryData al di fuori del confine <Activity>. I dati in cache saranno disponibili quando il componente nascosto torner\u00e0 visibile.
Activity scambia memoria per velocit\u00e0. Ogni albero di componenti nascosto rimane in memoria con il suo DOM completo. Per applicazioni con molte route nascoste, \u00e8 consigliabile monitorare il consumo di memoria. Il team React sta esplorando l'eliminazione automatica delle Activity nascoste meno utilizzate nelle versioni future.
Combinare useEffectEvent e Activity
Entrambe le API si completano a vicenda nei pattern di navigazione reali. Uno scenario comune: una dashboard con tab in cui ogni scheda ha subscription WebSocket e tracking analytics.
import { useEffect, useEffectEvent, useState } from 'react'
import { Activity } from 'react'
function LiveFeed({ channel, userId }: { channel: string; userId: string }) {
const [messages, setMessages] = useState<Message[]>([])
// Tracking analytics con userId corrente — nessuna risincronizzazione dell'effect
const onNewMessage = useEffectEvent((msg: Message) => {
analytics.track('live_message', { channel, userId })
setMessages((prev) => [...prev, msg])
})
useEffect(() => {
const ws = new WebSocket(`wss://api.example.com/${channel}`)
ws.onmessage = (event) => {
const msg = JSON.parse(event.data) as Message
onNewMessage(msg)
}
return () => ws.close()
}, [channel]) // Riconnessione pulita solo al cambio canale
return <MessageList messages={messages} />
}
export function LiveDashboard({ userId }: { userId: string }) {
const [activeChannel, setActiveChannel] = useState('general')
const channels = ['general', 'alerts', 'metrics']
return (
<div>
<nav className="flex gap-2">
{channels.map((ch) => (
<button key={ch} onClick={() => setActiveChannel(ch)}>
{ch}
</button>
))}
</nav>
{channels.map((ch) => (
<Activity key={ch} mode={activeChannel === ch ? 'visible' : 'hidden'}>
<LiveFeed channel={ch} userId={userId} />
</Activity>
))}
</div>
)
}Durante il cambio canale, il WebSocket del feed nascosto viene disconnesso (cleanup dell'effect tramite <Activity>). La cronologia dei messaggi sopravvive nello state. L'Effect Event onNewMessage garantisce che gli analytics facciano sempre riferimento alla userId corrente senza forzare riconnessioni WebSocket.
Domande da colloquio: useEffectEvent e Activity
Queste domande testano la comprensione delle nuove API e della loro interazione con il modello di rendering di React. Sono sempre pi\u00f9 comuni nei colloqui React presso aziende che utilizzano React 19.2.
D1: Quale problema risolve useEffectEvent che useCallback non pu\u00f2 risolvere?
useCallback crea una funzione memoizzata, che deve comunque essere inserita nel dependency array dell'effect. Se una delle sue dipendenze cambia, il callback cambia, il che riesegue l'effect. useEffectEvent crea una funzione che legge sempre i valori pi\u00f9 recenti senza essere una dipendenza: l'effect non viene mai rieseguito per causa sua. Questa separazione \u00e8 impossibile con il solo useCallback.
D2: Un Effect Event pu\u00f2 essere passato come prop a un componente figlio?
No. Gli Effect Event sono progettati per essere chiamati esclusivamente dall'interno di useEffect o di altri Effect Event. La loro identit\u00e0 cambia a ogni render, quindi passarli come props causerebbe re-render non necessari e infrangerebbe il modello mentale. Il plugin ESLint applica questa regola.
D3: In che modo Activity differisce dal rendering condizionale e da CSS display:none?
Il rendering condizionale ({show && <Component />}) smonta completamente il componente, distruggendo lo state. CSS display: none nasconde visivamente ma mantiene tutti gli effect in esecuzione, sprecando risorse. <Activity mode="hidden"> preserva lo state, esegue il cleanup degli effect, posticipa gli aggiornamenti a priorit\u00e0 idle e pu\u00f2 pre-renderizzare il contenuto in background.
D4: Cosa succede a useEffect all'interno di un Activity nascosto?
Quando un <Activity> passa a mode="hidden", React esegue tutte le funzioni di cleanup degli effect (il valore di ritorno di useEffect). Nessun nuovo effect viene montato finch\u00e9 il componente resta nascosto. Quando torna visibile, gli effect si rimontano con lo state preservato. Per questo le librerie di data fetching che si basano su useEffect necessitano di strategie di prefetching al di fuori del confine Activity.
D5: Come si pre-renderizza una route con Activity e Suspense?
La route va avvolta in <Activity mode="hidden"> con un boundary <Suspense> all'interno. Per il fetch dei dati si utilizza l'API use() o una sorgente dati compatibile con Suspense. React renderizza l'albero nascosto a bassa priorit\u00e0, risolvendo il boundary Suspense in background. Quando l'utente naviga e mode diventa "visible", il contenuto completamente renderizzato appare istantaneamente senza stato di caricamento.
D6: useEffectEvent \u00e8 un sostituto della regola lint exhaustive-deps?
No. La regola exhaustive-deps rimane fondamentale per individuare dipendenze genuinamente mancanti. useEffectEvent gestisce un caso specifico: logica che legge valori reattivi ma non deve controllare quando l'effect si riesegue (analytics, notifiche, logging). Utilizzarlo per sopprimere tutti gli avvisi sulle dipendenze nasconde bug e ne vanifica lo scopo.
Conclusione
useEffectEventsostituisce il workaround conuseRefper le stale closure negli effect, con supporto nativo del linter ineslint-plugin-react-hooks@6+- Gli Effect Event leggono sempre le props e lo state pi\u00f9 recenti senza attivare la risincronizzazione dell'effect \u2014 ideali per analytics, logging e callback di notifica
<Activity>preserva lo state dei componenti eseguendo il cleanup degli effect, offrendo un compromesso tra rendering condizionale e nascondimento CSS- Le Activity nascoste pre-renderizzano a priorit\u00e0 idle, abilitando la navigazione istantanea in combinazione con
Suspensee l'APIuse - TanStack Query e altre librerie basate su effect necessitano di prefetching al di fuori dei confini Activity, poich\u00e9
useEffectnon viene eseguito in modalit\u00e0 nascosta - Entrambe le API sono disponibili a partire da React 19.2 \u2014 aggiornare ESLint e React insieme per il pieno supporto degli strumenti
Inizia a praticare!
Metti alla prova le tue conoscenze con i nostri simulatori di colloquio e test tecnici.
Tag
Condividi
Articoli correlati

Next.js 16 Cache Components nel 2026: use cache, PPR e Domande da Colloquio per Sviluppatori Senior
Guida completa ai Cache Components di Next.js 16: direttiva use cache, Partial Pre-Rendering, cacheLife, cacheTag, sicurezza con use cache private e domande da colloquio tecnico per il 2026.

React Compiler nel 2026: Memoizzazione Automatica e Domande da Colloquio
Il React Compiler v1.0 introduce la memoizzazione automatica nelle applicazioni React. Questa guida analizza la pipeline di compilazione, le Rules of React, l'integrazione ESLint e le domande più frequenti nei colloqui tecnici sulla performance React nel 2026.

React Server Components in produzione: pattern e insidie
React Server Components in produzione: pattern collaudati, anti-pattern comuni e strategie di debug per applicazioni Next.js 15 robuste.