Dubinski vodič za React useEffectEvent: stale closures, pretplate, listeneri, timeri i analitika u Reactu 19.2
Praktični deep dive za React useEffectEvent u Reactu 19.2: kako ispraviti stale closures, smanjiti reconnect churn u pretplatama, držati listenere i timere ažurnima te dizajnirati čišće Effects za analitiku bez ref hackova i skrivanja dependency pravila.

React 2026 Primitives & Compiler Upgrade Guide
Ovo poglavlje fokusira se na React `useEffectEvent`: rje?avanje stale closures problema, dizajn Effecta, granice subscriptions, obrasce za listenere i timere, custom Hooks i analytics callbackove koji ostaju a?urni bez reconnect churna.
Svi članci u ovom vodiču
01
Pregled: React 2026 primitives i mentalni model ere compilera
?ira arhitekturna slika: ?to se promijenilo i gdje svaki primitive stvarno pripada.
02
Dubinski vodi? za useActionState: mutation flows, optimistic UI i integration patterns
Kada useActionState smanjuje boilerplate, a kada data layer i dalje ostaje pravi vlasnik problema.
03
React <Activity />: zadr?i state, pauziraj Effects i renderiraj u pozadini
Stvarni obrasci, performance trade-offovi i pitfalls za tabs, drawers i shell UI.
04
Dubinski vodi? za useEffectEvent: dizajn Effecta, subscriptions i analitika
Linter-friendly granice Effecta bez stale closures i reconnect churna.
što ćete dobiti
Ovo poglavlje govori o dizajnu Effecta, a ne o trivijalnostima oko Hookova. Pitanje nije “gdje mogu koristiti useEffectEvent?”, nego “koji su dijelovi ovog Effecta stvarno reaktivni, a koji su samo reakcije na događaje kojima trebaju svježe vrijednosti?”
Stale-closure bug je simptom koji svi prvo primijete: callback unutar Effecta čita stare vrijednosti. No dublji problem je arhitektonski. Timovi često trpaju dva različita posla u jedan Effect.
Prvi posao je sinkronizacija: spojiti se na chat room, attachati listener, pokrenuti timer, subscribati se na SDK ili inicijalizirati browser API. Drugi posao je logika odgovora: prikazati notifikaciju s aktualnom temom, uključiti najnovije analytics metapodatke, provjeriti mute flag ili formatirati podatke za trenutačnu locale. Ti poslovi stare različito i ne bi uvijek trebali reagirati na isti skup dependencyja. [1][2][5]
Prije useEffectEvent, timovi su tu napetost obično rješavali ili preširokim dependency arrayima koji su stvarali reconnect churn, ili mutable refovima koji su popravljali stale reads, ali su dizajn činili težim za razumjeti u code reviewu. useEffectEvent postoji kako bi taj razdor učinio eksplicitnim. [1][2]
React 19.2 uvodi useEffectEvent kao jednu od ključnih mogućnosti izdanja. [2]
Osnovna React smjernica i dalje vrijedi: Effects služe za sinkronizaciju s vanjskim sustavima, a ne za svaku promjenu statea ili user action. [5]
Snimka zaslona sekcije why-this-hook-existsAko tim treba zapamtiti samo jedan dio ovog članka, neka to bude upravo ovaj.
Pronađite vanjski sustav
Effect bi se trebao sinkronizirati s nečim izvan Reacta: subscriptionom, timerom, DOM listenerom, browser API-jem ili SDK-om. Ako vanjski sustav ne postoji, React dokumentacija kaže da vam Effect vjerojatno uopće ne treba. [5]
Premjestite event-like logiku odgovora u useEffectEvent
Jednostavan test
Pitajte se: “Ako se ova vrijednost promijeni, trebam li se reconnectati ili ponovno attachati listener?” Ako da, ostavite je kao dependency. Ako ne, ali callback i dalje treba najnoviju vrijednost kad se događaj dogodi, tada useEffectEvent počinje imati smisla. [1]
Službena React objašnjenja stalno se vraćaju primjerima chat roomova jer oni problem otkrivaju odmah. Connection treba biti reaktivan na roomId. Notifikacija i dalje treba vidjeti najnoviju theme ili drugi UI state. To su dvije različite odgovornosti.
Kada ih modelirate odvojeno, effect boundary postaje lakši za review: Effect posjeduje setup i teardown connectiona, a Effect Event posjeduje logiku što napraviti kad connection emitira događaj. [1][2]
import { useEffect, useEffectEvent } from "react";
type Props = {
roomId: string;
theme: "light" | "dark";
};
declare function createConnection(roomId: string): {
on(event: "connected", cb: () => void): void;
connect(): void;
disconnect(): void;
};
declare function showNotification(message: string, theme: Props["theme"]): void;
export function ChatRoom({ roomId, theme }: Props) {
const onConnected = useEffectEvent(() => {
showNotification("Connected!", theme);
});
useEffect(() => {
const connection = createConnection(roomId);
connection.on("connected", () => {
onConnected();
});
connection.connect();
return () => connection.disconnect();
}, [roomId]);
return null;
}Ovo je najčišći “aha” trenutak ovog API-ja: najnovija vrijednost i reaktivni dependency nisu uvijek ista stvar. [1][2]
Subscriptions su mjesto gdje ovaj obrazac odmah donosi vrijednost. Chat, live metrics, billing streams, Firebase-like listeneri, media API-ji i third-party SDK-ovi dijele isti failure mode: setup bi trebao biti vezan uz mali skup vrijednosti, dok callback često želi širi skup “latest” UI values.
Čest smell je dependency array koji raste iz sprinta u sprint: roomId, serverUrl, theme, muted, locale, trackingContext, featureFlags, plan, segment. U tom trenutku Effect više ne opisuje external synchronization boundary; opisuje sve što je slučajno referencirano unutar ugniježđenog callbacka.
React također pokazuje da useEffectEvent posebno dobro radi unutar reusable hookova. Custom hook može omotati proslijeđeni callback u useEffectEvent, pa sam hook ostaje subscriban samo na stvarne connection inputs i ne prisiljava reconnect svaki put kad parent rerenderira s novim callback identityjem. [1][4]
import { useEffect, useEffectEvent } from "react";
type Message = { id: string; body: string };
type Options = {
serverUrl: string;
roomId: string;
muted: boolean;
locale: string;
onReceiveMessage: (message: Message) => void;
};
declare function createConnection(options: {
serverUrl: string;
roomId: string;
}): {
connect(): void;
disconnect(): void;
on(event: "message", cb: (message: Message) => void): void;
};
declare function playSound(name: "message"): void;
declare function formatMessagePreview(message: Message, locale: string): Message;
export function useChatRoom({
serverUrl,
roomId,
muted,
locale,
onReceiveMessage,
}: Options): void {
const onMessage = useEffectEvent((message: Message) => {
const nextMessage = formatMessagePreview(message, locale);
if (!muted) {
playSound("message");
}
onReceiveMessage(nextMessage);
});
useEffect(() => {
const connection = createConnection({ serverUrl, roomId });
connection.connect();
connection.on("message", (message) => {
onMessage(message);
});
return () => connection.disconnect();
}, [serverUrl, roomId]);
}React vodič za custom Hooks prikazuje jedan od najkorisnijih production slučajeva: držite subscription vezan za stvarne connection inputs, a ne za callback identity. [4]
Snimka zaslona sekcije subscriptionsAnalytics kod je mjesto gdje timovi najčešće žele dvije kontradiktorne stvari u isto vrijeme: stabilne subscriptions i svjež context.
Zamislite listener video playera, billing event stream, observer callback ili websocket event. Sama subscription trebala bi ovisiti o identitetu resursa: videu, računu, observer targetu ili channelu. Ali tracking payload koji se emitira trebao bi uključivati najnovije plan, experiment bucket, locale, route metadata ili naziv product surfacea. To je trenutačni context, a ne identity connectiona.
Baš je ovdje useEffectEvent lakše razumjeti od obrasca poput ref.current = latestStuff. Ref može raditi, ali skriva namjeru. Effect Event u kodu jasno kaže: “ovaj callback je dio priče Effecta, ali nije synchronization dependency.” [1][2]
import { useEffect, useEffectEvent } from "react";
type Props = {
accountId: string;
plan: "free" | "pro" | "team";
experiment: string;
routeName: string;
};
declare function subscribeToBillingEvents(
accountId: string,
onEvent: (eventName: string) => void,
): () => void;
declare function track(event: string, props: Record<string, string>): void;
export function BillingAnalytics({
accountId,
plan,
experiment,
routeName,
}: Props) {
const onBillingEvent = useEffectEvent((eventName: string) => {
track("billing_event", {
eventName,
plan,
experiment,
routeName,
});
});
useEffect(() => {
return subscribeToBillingEvents(accountId, (eventName) => {
onBillingEvent(eventName);
});
}, [accountId]);
return null;
}Ovaj obrazac posebno dobro funkcionira u product analyticsu jer jasno odvaja “na što smo spojeni?” od “kojim podacima trebamo označiti ovaj događaj upravo sada?”.
Timeri, listeneri, observeri i SDK callbackovi postavljaju isto pitanje: koje vrijednosti trebaju upravljati setupom i cleanupom, a koje samo treba pročitati kad se nešto dogodi? [1]
| Comparison point | Ostavite reaktivnim u Effectu | Premjestite u useEffectEvent |
|---|---|---|
| Interval | Pokretanje i čišćenje timera, plus vrijednosti poput enabled ili delay koje ga trebaju ponovno kreirati | Što se događa na svakom ticku kada toj logici trebaju najnoviji props ili state |
| Window listener | Attach i remove listenera, zajedno sa stvarnim event targetom i event typeom | Handler logika kojoj trebaju najnoviji filters, labels, locale ili mute flags |
| Observer callback | Stvaranje i disconnect observera, plus njegov target i observer options | Najnoviji analytics metadata ili UI reaction kada se callback observera pokrene |
| Third-party SDK subscription | SDK inicijalizacija i teardown, plus resource identity koji definira subscription | Najnoviji UI context ili event formatting korišten kad SDK nešto emitira |
Praktično čitanje
Neka wiring pripada Effectu. Neka odgovor na događaj, koji mora ostati ažuran bez širenja synchronization boundaryja, pripada useEffectEvent. [1]
Dokumentacija sadrži nekoliko detalja koje je lako preskočiti, ali u većim codebaseovima oni su stvarno važni.
Vrijedi posebno naglasiti ovu zadnju točku, jer je useEffectEvent vrlo lako početi koristiti preširoko. On poboljšava dizajn Effecta, ali ne opravdava postojanje Effecta koji nije trebao biti napisan.
službeno podržani
React eksplicitno podržava korištenje useEffectEvent unutar vlastitih custom Hookova. To je velika stvar za reusable API-je oko subscriptions i listeners. [1][4]
držite ga lokalnim
Effect Events trebaju se pozivati samo iz Effects ili drugih Effect Events unutar iste komponente ili hooka. Nemojte ih prosljeđivati kao obične callback props. [1]
custom effect hooks
React hooks plugin dokumentira shared settings additionalEffectHooks za custom effect hooks, što timovima pomaže dosljedno lintati wrapper hookove. [3][7]
često je to najbolje rješenje
React smjernica “You Might Not Need an Effect” i dalje vrijedi. Ponekad pravi refaktor nije useEffectEvent, nego potpuno izbacivanje logike iz Effecta koji nije trebao postojati. [5]
Reference stranica jasno definira granice: Effect Events su lokalni za Effects, namjerno su isključeni iz dependency arraya i nisu general-purpose callback primitive. [1]
Snimka zaslona sekcije interesting-detailsSam Hook je malen. Većina grešaka nastaje kada ga se koristi kao prečac, a ne kao alat za dizajn.
Korištenje useEffectEvent za skrivanje vrijednosti koja bi stvarno trebala ponovno pokrenuti Effect. Ako promjena te vrijednosti treba ponovno subscribati, ponovno attachati ili reconnectati, to je i dalje dependency. [1]
Tretiranje Effect Eventa kao običnih event handlera ili njihovo prosljeđivanje kroz props. React reference je izričit: trebaju se pozivati iz Effects ili drugih Effect Events, a ne iz render logike ili proizvoljnih component boundaryja. [1]
Zadržavanje golemih Effecta i premještanje nasumičnih linija u Effect Event dok linter ne utihne. To nije refaktor, to je dependency laundering.
Preskakanje pitanja “treba li mi ovaj Effect uopće?”. React dokumentacija i dalje preporučuje brisanje nepotrebnih Effects umjesto ukrašavanja novim API-jima. [5]
Najjednostavnija provjera razuma
Ako nakon refaktora ne možete jednom rečenicom objasniti synchronization boundary, Effect vjerojatno i dalje radi previše stvari.
Ove opcije rješavaju različite probleme. Ako ih tretirate kao međusobno zamjenjive, effect kod brzo postaje neuredan.
Koristite useEffectEvent
Zadržite šire dependencyje
To je ispravno kada promijenjena vrijednost stvarno mijenja synchronization boundary. Reconnect nije bug ako se sama connection stvarno promijenila. [1]
Koristite ref
I dalje je valjan escape hatch za neke scenarije s mutability, ali je za event-like callback logiku obično manje jasan od Effect Eventa. Koristite ga kad je problem mutability, a ne effect event design. [1]
Izbrišite Effect
Najbolja opcija kada ne postoji vanjski sustav i logika pripada render kodu, derived stateu ili običnom event handleru. To je često i najčišći i najbrži fix. [5]
Koristite ovo tijekom React 19.2 cleanup prolaza ili pri reviewju custom Hookova koji omataju subscriptions i listenere.
Napravite audit svakog Effecta koji dotiče vanjski sustav.
Počnite sa sockets, listeners, timers, observers, media APIs i SDK wrappers. To su najvrjedniji kandidati za pregled. [5]
Zapišite stvarni synchronization boundary.
Imenujte vrijednosti koje stvarno trebaju uzrokovati novi setup i cleanup.
Koristite useEffectEvent samo za event-like callback logiku.
Ako se logika ne pokreće iz Effecta ili drugog Effect Eventa, vjerojatno pripada negdje drugdje. [1]
Brišite Effects koji se ne sinkroniziraju s vanjskim sustavom.
Ne pretvarajte nepotrebne Effects u “pametne” Effects. Samo ih uklonite. [5]
Testirajte i listener churn i freshness callbackova.
Cilj je i manje nepotrebnih teardownova i ispravno latest-value ponašanje kada se događaj stvarno dogodi.
Očekivani rezultat
Nakon dobrog rollouta Effects postaju lakši za objasniti, lakši za lintanje i rjeđe se reconnectaju zbog nerelevantnog presentational statea.
Omogućuje event-like logici koja se pokreće iz Effecta da čita najnovije props i state bez ponovne sinkronizacije cijelog okolnog Effecta. Posebno je koristan za subscriptions, listenere, timere i analytics callbackove. [1][2]
Ne. Vrijednosti koje stvarno definiraju synchronization boundary i dalje moraju ostati u dependency arrayu. `useEffectEvent` pomaže kada callback logika treba najnovije vrijednosti, ali te vrijednosti ne bi smjele ponovno pokretati subscription ili listener. [1]
Ne uvijek. Ref i dalje ima legitimne escape-hatch slučajeve. Ali za event-like logiku koja se pokreće iz Effecta, `useEffectEvent` obično jasnije komunicira namjeru i bolje se uklapa u službeni React effect model. [1][2]
Da. React eksplicitno dokumentira taj obrazac i to je jedan od najkorisnijih production slučajeva jer omogućuje reusable hookovima da subscriptions ostanu stabilne, a da se pritom uvijek poziva najnoviji consumer callback. [1][4]
Ne. Effect Events trebaju se pozivati iz Effects ili drugih Effect Eventa unutar iste komponente ili hooka, a ne prosljeđivati kao obične callback props. [1]
Korištenje `useEffectEvent` kao prečaca za dependencyje umjesto da se pitate što stvarno definira synchronization boundary. Druga najčešća greška je da se nepotrebni Effects ne obrišu odmah na početku. [1][5]
Da, uz ispravnu konfiguraciju. React hooks plugin dokumentira shared settings `additionalEffectHooks` kako bi se custom effect hooks mogli dosljedno lintati. [3][7]
Uključujemo samo izvore koji izravno podupiru savjete, caveats i primjere korištene u ovom poglavlju.
Velik dio boli kod React nadogradnji nije sintaksa. To je dizajnerski dug skriven unutar Effecta: pomiješane odgovornosti, bučni dependencyji, reconnect churn i analitika zalijepljena na pogrešan sloj.
PAS7 Studio može pomoći auditirati te granice, prepoznati koje Effects treba izbrisati, a koje redizajnirati, i pretvoriti prelazak na React 19.2 u migracijski plan s pravim inženjerskim guardrailsima.
Dubinski vodi? za useEffectEvent: dizajn Effecta, subscriptions i analitika
Vi ste ovdje: 04/04
Dubinski vodi? za useEffectEvent: dizajn Effecta, subscriptions i analitika