React 19 useEffectEvent e Activity: novas APIs e perguntas de entrevista 2026
Analise aprofundada das APIs useEffectEvent e Activity do React 19.2. Solucoes para stale closures, pre-renderizacao em segundo plano, exemplos de codigo e perguntas de entrevista tecnica.

O React 19.2 trouxe duas APIs que resolvem problemas antigos e recorrentes no desenvolvimento de aplicacoes React: o useEffectEvent elimina stale closures dentro de efeitos, e o componente <Activity> permite pre-renderizacao em segundo plano com preservacao de estado. Ambas foram lancadas em outubro de 2025 e ja estao transformando a forma como aplicacoes React em producao lidam com efeitos colaterais e navegacao entre rotas.
useEffectEvent e Activity exigem React 19.2 ou superior. A atualizacao pode ser feita com npm install react@latest react-dom@latest. O plugin de ESLint eslint-plugin-react-hooks@6+ adiciona suporte nativo para useEffectEvent nos arrays de dependencias.
O problema das stale closures em efeitos
Todo desenvolvedor React ja enfrentou o problema de stale closures. Um efeito captura um valor no momento da renderizacao e, quando esse valor muda, o efeito continua referenciando a versao antiga. A solucao classica envolvia o uso de useRef para manter uma referencia mutavel -- funcional, porem verbosa e invisivel para o linter.
O exemplo a seguir ilustra uma aplicacao de chat que registra analytics ao receber uma nova mensagem. O log precisa incluir o theme atual, mas mudancas no theme nao devem forcar uma reconexao do chat:
// Before useEffectEvent: useRef workaround
import { useEffect, useRef } from 'react'
export function useChatRoom(roomId: string, theme: string) {
// Store theme in a ref to avoid stale closure
const themeRef = useRef(theme)
themeRef.current = theme
useEffect(() => {
const connection = createConnection(roomId)
connection.on('message', (msg) => {
// themeRef.current always has the latest value
logAnalytics('new_message', { roomId, theme: themeRef.current })
showNotification(msg)
})
connection.connect()
return () => connection.disconnect()
}, [roomId]) // theme intentionally excluded — but linter warns
}Essa abordagem funciona, mas o linter sinaliza theme como dependencia ausente. Suprimir o aviso com um comentario oculta bugs potenciais em outras partes do codigo. Alem disso, o padrao com useRef obscurece a intencao: um desenvolvedor novo na equipe precisa rastrear mentalmente por que theme esta armazenado em um ref em vez de constar no array de dependencias.
useEffectEvent: separando logica reativa de nao reativa
O hook useEffectEvent cria uma funcao estavel que sempre le os valores mais recentes de props e estado, sem provocar a re-sincronizacao do efeito. Esse hook substitui o padrao com useRef por uma API declarativa que o linter compreende nativamente.
// After useEffectEvent: clean separation of concerns
import { useEffect, useEffectEvent } from 'react'
export function useChatRoom(roomId: string, theme: string) {
// Effect Event: always reads latest theme, never triggers 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]) // No linter warning — onMessage is an Effect Event
}O efeito so re-executa quando roomId muda. O callback onMessage sempre acessa o theme atual sem precisar constar como dependencia. A documentacao oficial do React estabelece explicitamente que Effect Events devem ser chamados apenas dentro de efeitos -- nunca durante a renderizacao ou passados como props para componentes filhos.
Regras e restricoes do useEffectEvent
Tres regras governam o uso do useEffectEvent:
- A chamada deve ocorrer no nivel superior do componente ou custom hook -- nunca dentro de loops ou condicoes
- A funcao retornada so pode ser chamada dentro de
useEffectou de outro Effect Event - A funcao nunca deve ser passada como prop nem retornada de um hook para consumo externo
Violar a regra 2 causa bugs sutis: a identidade da funcao muda a cada renderizacao, entao armazena-la em um ref ou passa-la para componentes filhos anula o proposito do hook. O eslint-plugin-react-hooks@6+ impoe essas restricoes automaticamente.
// Correct: Effect Event used inside useEffect
import { useEffect, useEffectEvent, useState } from 'react'
interface SearchTrackerProps {
query: string
userId: string
}
export function SearchTracker({ query, userId }: SearchTrackerProps) {
const [results, setResults] = useState<string[]>([])
// Track searches with current user context
const onSearchComplete = useEffectEvent((resultCount: number) => {
analytics.track('search_complete', {
query,
userId, // Always latest userId
resultCount,
timestamp: Date.now(),
})
})
useEffect(() => {
const controller = new AbortController()
fetchSearchResults(query, controller.signal).then((data) => {
setResults(data)
onSearchComplete(data.length) // Called from inside useEffect
})
return () => controller.abort()
}, [query]) // userId excluded safely — lives in the Effect Event
return <ResultsList results={results} />
}useEffectEvent nao e uma escapatoria para silenciar o linter de dependencias. Se um valor genuinamente controla quando um efeito deve re-executar, esse valor pertence ao array de dependencias. A extracao de logica para um Effect Event so se justifica quando ela representa uma acao lateral (logging, notificacao, analytics) que le valores reativos sem precisar re-disparar o efeito.
O componente Activity: pre-renderizacao com preservacao de estado
O componente <Activity> (anteriormente chamado de "Offscreen") controla a visibilidade dos seus filhos. Diferentemente da renderizacao condicional, que destroi o estado, ou do CSS display: none, que mantem efeitos em execucao, o <Activity> preserva o estado enquanto limpa efeitos e adia atualizacoes.
// Activity preserves form state across tab switches
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>
)
}Um usuario preenchendo um formulario na aba "Perfil" pode alternar para "Configuracoes" e voltar sem perder os dados digitados. Os efeitos da aba oculta (timers, subscriptions, data fetching) sao limpos, liberando recursos. Quando a aba se torna visivel novamente, os efeitos sao remontados e o estado e restaurado instantaneamente.
Pronto para mandar bem nas entrevistas de React / Next.js?
Pratique com nossos simuladores interativos, flashcards e testes tecnicos.
Modos do Activity: visivel vs oculto em detalhe
O componente <Activity> aceita a prop mode com dois valores possiveis:
| Comportamento | mode="visible" | mode="hidden" |
|----------|-------------------|------------------|
| Renderizacao do DOM | Normal | display: none via CSS |
| Estado do componente | Ativo | Preservado em memoria |
| Efeitos (useEffect) | Montados | Limpos |
| Prioridade de atualizacoes | Normal | Adiada para idle |
| Pre-renderizacao | N/A | Renderizado em baixa prioridade |
Quando um componente inicia oculto (renderizacao inicial com mode="hidden"), o React o pre-renderiza com prioridade baixa sem montar efeitos. Isso possibilita navegacao instantanea: a pagina de destino ja esta renderizada em segundo plano quando o usuario clica.
// Pre-render the next likely route in the 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() reads the cached promise — works during hidden pre-render
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>
)
}O <Activity> oculto pre-renderiza o dashboard com prioridade ociosa. Combinado com Suspense e a API use, o carregamento de dados ocorre em segundo plano. Quando isActive muda para true, o conteudo aparece sem nenhum spinner de carregamento.
Activity e TanStack Query: a armadilha do cache
Uma armadilha comum ao usar <Activity> envolve o TanStack Query. Como o useQuery depende internamente de useEffect, queries dentro de um <Activity> oculto nao serao executadas -- o efeito esta desmontado.
// Problem: useQuery won't fetch when Activity is hidden
import { useQuery, useQueryClient } from '@tanstack/react-query'
import { Activity } from 'react'
function UserStats() {
// This useQuery will NOT run while hidden
const { data } = useQuery({
queryKey: ['user-stats'],
queryFn: fetchUserStats,
})
return <StatsDisplay data={data} />
}
// Solution: prefetch data outside the Activity boundary
function DashboardWithPrefetch({ showStats }: { showStats: boolean }) {
const queryClient = useQueryClient()
// Prefetch at the parent level — runs regardless of Activity mode
queryClient.ensureQueryData({
queryKey: ['user-stats'],
queryFn: fetchUserStats,
})
return (
<Activity mode={showStats ? 'visible' : 'hidden'}>
<UserStats />
</Activity>
)
}A solucao e direta: mover o prefetching de dados para um componente pai ou usar queryClient.ensureQueryData fora da fronteira do <Activity>. Os dados em cache estarao disponiveis quando o componente oculto se tornar visivel.
O Activity troca memoria por velocidade. Cada arvore de componentes oculta permanece na memoria com seu DOM completo. Para aplicacoes com muitas rotas ocultas, o consumo de memoria deve ser monitorado. A equipe do React esta explorando a evicao automatica de Activities ocultos menos utilizados em versoes futuras.
Combinando useEffectEvent e Activity
Ambas as APIs se complementam em padroes reais de navegacao. Um cenario comum e um dashboard com abas onde cada aba mantem subscricoes WebSocket e rastreamento de analytics.
import { useEffect, useEffectEvent, useState } from 'react'
import { Activity } from 'react'
function LiveFeed({ channel, userId }: { channel: string; userId: string }) {
const [messages, setMessages] = useState<Message[]>([])
// Analytics tracking with latest userId — no effect re-sync
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]) // Clean reconnect only when channel changes
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>
)
}Ao trocar de canal, o WebSocket do feed oculto e desconectado (limpeza de efeito via <Activity>). O historico de mensagens sobrevive no estado. O Effect Event onNewMessage garante que o analytics sempre referencia o userId atual sem forcar reconexoes do WebSocket.
Perguntas de entrevista: useEffectEvent e Activity
As perguntas a seguir testam a compreensao das novas APIs e sua interacao com o modelo de renderizacao do React. Elas aparecem com frequencia crescente em entrevistas React de empresas que utilizam React 19.2.
P1: Qual problema o useEffectEvent resolve que o useCallback nao consegue?
O useCallback cria uma funcao memoizada, mas essa funcao ainda precisa ser listada no array de dependencias do efeito. Se alguma de suas proprias dependencias mudar, o callback muda, o que re-dispara o efeito. O useEffectEvent cria uma funcao que sempre le os valores mais recentes sem ser uma dependencia -- o efeito nunca re-executa por causa dela. Essa separacao e impossivel apenas com useCallback.
P2: Um Effect Event pode ser passado como prop para um componente filho?
Nao. Effect Events foram projetados para serem chamados apenas dentro de useEffect ou de outros Effect Events. A identidade da funcao muda a cada renderizacao, entao passa-la como prop causaria re-renderizacoes desnecessarias e quebraria o modelo mental. O plugin de ESLint impoe essa regra.
P3: Como o Activity se diferencia da renderizacao condicional e do CSS display:none?
A renderizacao condicional ({show && <Component />}) desmonta o componente por completo -- o estado e destruido. O CSS display: none oculta visualmente mas mantem todos os efeitos em execucao, consumindo recursos desnecessariamente. O <Activity mode="hidden"> preserva o estado, limpa efeitos, adia atualizacoes para prioridade ociosa e pode pre-renderizar conteudo em segundo plano.
P4: O que acontece com o useEffect dentro de um Activity oculto?
Quando um <Activity> transiciona para mode="hidden", o React executa todas as funcoes de limpeza de efeitos (o retorno do useEffect). Nenhum novo efeito e montado enquanto o componente permanece oculto. Quando se torna visivel novamente, os efeitos sao remontados com o estado preservado. Esse e o motivo pelo qual bibliotecas de data fetching que dependem de useEffect precisam de estrategias de prefetching fora da fronteira do Activity.
P5: Como pre-renderizar uma rota com Activity e Suspense?
O componente da rota deve ser envolvido com <Activity mode="hidden"> e um <Suspense> boundary interno. A API use() ou uma fonte de dados compativel com Suspense e utilizada para o carregamento. O React renderiza a arvore oculta com prioridade baixa, resolvendo o Suspense boundary em segundo plano. Quando o usuario navega e o mode muda para "visible", o conteudo totalmente renderizado aparece instantaneamente, sem estado de carregamento.
P6: O useEffectEvent substitui a regra exhaustive-deps do linter?
Nao. A regra exhaustive-deps continua sendo essencial para capturar dependencias genuinamente ausentes. O useEffectEvent trata um caso especifico: logica que le valores reativos mas nao deve controlar quando o efeito re-executa (analytics, notificacoes, logging). Usa-lo para suprimir todos os avisos de dependencia oculta bugs e anula o proposito da API.
Conclusao
- O
useEffectEventsubstitui o padrao comuseRefpara stale closures em efeitos, com suporte nativo no linter viaeslint-plugin-react-hooks@6+ - Effect Events sempre leem as props e o estado mais recentes sem disparar a re-sincronizacao do efeito -- a utilizacao ideal envolve callbacks de analytics, logging e notificacoes
- O
<Activity>preserva o estado dos componentes enquanto limpa efeitos, oferecendo um meio-termo entre renderizacao condicional e ocultacao via CSS - Activities ocultos pre-renderizam com prioridade ociosa, viabilizando navegacao instantanea quando combinados com
Suspensee a APIuse - O TanStack Query e outras bibliotecas baseadas em efeitos exigem prefetching fora das fronteiras do Activity, ja que o
useEffectnao executa no modo oculto - Ambas as APIs estao disponiveis a partir do React 19.2 -- a atualizacao do ESLint e do React deve ser feita em conjunto para suporte completo das ferramentas
Comece a praticar!
Teste seus conhecimentos com nossos simuladores de entrevista e testes tecnicos.
Tags
Compartilhar
Artigos relacionados

React Compiler em 2026: memoização automática e perguntas de entrevista
Análise completa do React Compiler em 2026: pipeline de compilação, memoização automática, regras do React e perguntas frequentes em entrevistas técnicas.

React Server Components em produção: padrões e armadilhas
React Server Components em produção: padrões testados em batalha, anti-padrões comuns e estratégias de depuração para aplicações Next.js 15 robustas.

React 19: Server Components em producao - O guia completo
Dominar os Server Components do React 19 em producao. Arquitetura, padroes, streaming, caching e otimizacoes para aplicacoes de alta performance.