# React 19 useEffectEvent en Activity: Nieuwe API's en Sollicitatievragen 2026 > Uitgebreide gids over useEffectEvent en het Activity-component in React 19.2 – met codevoorbeelden, best practices en veelgestelde sollicitatievragen voor 2026. - Published: 2026-06-03 - Updated: 2026-06-03 - Author: SharpSkill - Tags: react, react-19, useEffectEvent, activity, sollicitatie - Reading time: 9 min --- React 19.2 heeft twee API's ge\u00efntroduceerd die al lang bestaande pijnpunten aanpakken: `useEffectEvent` elimineert stale closures in effects, en `` maakt achtergrond pre-rendering mogelijk met volledige state-behoud. Beide API's zijn uitgebracht in [oktober 2025](https://react.dev/blog/2025/10/01/react-19-2) en veranderen nu al hoe productie React-applicaties side effects en navigatie afhandelen. > **Minimaal React 19.2** > > useEffectEvent en Activity vereisen React 19.2 of hoger. Update met `npm install react@latest react-dom@latest`. De ESLint-plugin `eslint-plugin-react-hooks@6+` voegt native ondersteuning toe voor useEffectEvent in dependency arrays. ## Het stale closure-probleem: waarom useEffectEvent nodig is Iedere React-ontwikkelaar heeft wel eens te maken gehad met stale closures. Een effect legt een waarde vast op het moment van rendering, en wanneer die waarde verandert, verwijst het effect nog steeds naar de oude. De klassieke workaround was het gebruik van `useRef` om een muteerbare referentie vast te houden \u2014 werkend, maar omslachtig en onzichtbaar voor de linter. Neem een chatapplicatie die analytics registreert wanneer een nieuw bericht binnenkomt. Het log moet het huidige `theme` bevatten, maar theme-wijzigingen mogen de chatverbinding niet opnieuw opzetten: ```tsx // hooks/useChatRoom.ts // V\u00f3\u00f3r useEffectEvent: useRef-workaround import { useEffect, useRef } from 'react' export function useChatRoom(roomId: string, theme: string) { // Theme opslaan in een ref om stale closure te voorkomen const themeRef = useRef(theme) themeRef.current = theme useEffect(() => { const connection = createConnection(roomId) connection.on('message', (msg) => { // themeRef.current heeft altijd de actuele waarde logAnalytics('new_message', { roomId, theme: themeRef.current }) showNotification(msg) }) connection.connect() return () => connection.disconnect() }, [roomId]) // theme bewust weggelaten — maar de linter waarschuwt } ``` Deze aanpak werkt, maar de linter markeert `theme` als een ontbrekende dependency. Het onderdrukken van de waarschuwing verbergt potenti\u00eble bugs elders. Het `useRef`-patroon verhult ook de intentie: een nieuwe ontwikkelaar die deze code leest, moet mentaal reconstrueren waarom `theme` in een ref zit in plaats van in de dependency array. ## useEffectEvent: reactieve en niet-reactieve logica scheiden De `useEffectEvent`-hook cre\u00ebert een stabiele functie die altijd de meest recente props en state leest, zonder de effect-resynchronisatie te activeren. Het vervangt het `useRef`-patroon door een declaratieve API die de linter native begrijpt. ```tsx // hooks/useChatRoom.ts // Na useEffectEvent: schone scheiding van verantwoordelijkheden import { useEffect, useEffectEvent } from 'react' export function useChatRoom(roomId: string, theme: string) { // Effect Event: leest altijd het huidige theme, veroorzaakt geen 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]) // Geen linter-waarschuwing — onMessage is een Effect Event } ``` Het effect wordt alleen opnieuw uitgevoerd wanneer `roomId` verandert. De `onMessage`-callback ziet altijd het huidige `theme` zonder als dependency te worden vermeld. De [offici\u00eble React-documentatie](https://react.dev/reference/react/useEffectEvent) stelt expliciet dat Effect Events alleen vanuit effects mogen worden aangeroepen \u2014 nooit tijdens rendering of als props doorgegeven aan child-componenten. ## Regels en beperkingen van useEffectEvent Drie regels bepalen het gebruik van `useEffectEvent`: 1. Aanroepen op het hoogste niveau van een component of custom hook \u2014 nooit binnen loops of condities 2. De geretourneerde functie mag alleen worden aangeroepen vanuit `useEffect` of een ander Effect Event 3. De functie mag niet als prop worden doorgegeven of worden geretourneerd door een hook voor extern gebruik Het overtreden van regel 2 veroorzaakt subtiele bugs: de functie-identiteit verandert bij elke render, dus het opslaan in een ref of doorgeven naar beneden ondermijnt het doel. De `eslint-plugin-react-hooks@6+` [handhaaft deze beperkingen](https://react.dev/reference/react/useEffectEvent) automatisch. ```tsx // components/SearchTracker.tsx // Correct: Effect Event gebruikt binnen useEffect import { useEffect, useEffectEvent, useState } from 'react' interface SearchTrackerProps { query: string userId: string } export function SearchTracker({ query, userId }: SearchTrackerProps) { const [results, setResults] = useState([]) // Zoekopdrachten bijhouden met de huidige gebruikerscontext const onSearchComplete = useEffectEvent((resultCount: number) => { analytics.track('search_complete', { query, userId, // Altijd de actuele userId resultCount, timestamp: Date.now(), }) }) useEffect(() => { const controller = new AbortController() fetchSearchResults(query, controller.signal).then((data) => { setResults(data) onSearchComplete(data.length) // Aangeroepen vanuit useEffect }) return () => controller.abort() }, [query]) // userId veilig weggelaten — zit in het Effect Event return } ``` > **Geen vervanging voor de dependency-linter** > > useEffectEvent is geen ontsnappingsroute om de dependency-linter het zwijgen op te leggen. Als een waarde daadwerkelijk bepaalt wanneer een effect opnieuw moet worden uitgevoerd, hoort deze in de dependency array. Logica hoort alleen in een Effect Event te worden ge\u00ebxtraheerd wanneer het een nevenactie betreft (logging, notificaties, analytics) die reactieve waarden leest zonder het effect opnieuw te hoeven activeren. ## Het Activity-component: achtergrond pre-rendering met state-behoud Het ``-component (voorheen "Offscreen" genoemd) bepaalt of zijn children zichtbaar of verborgen zijn. In tegenstelling tot conditioneel rendering dat state vernietigt, of CSS `display: none` dat effects laat draaien, behoudt `` de state terwijl het effects opruimt en updates uitstelt. ```tsx // components/TabLayout.tsx // Activity behoudt formulier-state bij tab-wisseling 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 (
{tabs.map((tab) => ( {tab.content} ))}
) } ``` Wanneer een gebruiker een formulier invult op het tabblad "Profiel", kan deze overschakelen naar "Instellingen" en terugkeren zonder invoer te verliezen. De effects van het verborgen tabblad (timers, subscriptions, data fetching) worden opgeruimd en resources worden vrijgegeven. Wanneer het tabblad weer zichtbaar wordt, worden effects opnieuw gemount en wordt de state direct hersteld. ## Activity-modi: visible en hidden onder de motorkap Het ``-component accepteert een `mode`-prop met twee waarden: | Gedrag | `mode="visible"` | `mode="hidden"` | |----------|-------------------|------------------| | DOM-rendering | Normaal | `display: none` via CSS | | Component-state | Actief | Behouden in geheugen | | Effects (useEffect) | Gemount | Opgeruimd | | Update-prioriteit | Normaal | Uitgesteld naar idle | | Pre-rendering | N.v.t. | Rendering met lage prioriteit | Wanneer een component verborgen start (eerste render met `mode="hidden"`), rendert React het met lage prioriteit zonder effects te mounten. Dit maakt directe navigatie mogelijk: de doelpagina is al op de achtergrond gerenderd wanneer de gebruiker klikt. ```tsx // components/PrerenderedRoute.tsx // De volgende waarschijnlijke route op de achtergrond pre-renderen import { Activity, Suspense, use } from 'react' interface PrerenderedRouteProps { isActive: boolean dataPromise: Promise } export function PrerenderedRoute({ isActive, dataPromise }: PrerenderedRouteProps) { return ( }> ) } function DashboardContent({ dataPromise }: { dataPromise: Promise }) { // use() leest de gecachte promise — werkt tijdens verborgen pre-render const data = use(dataPromise) return (
) } ``` De verborgen `` pre-rendert het dashboard op idle-prioriteit. Gecombineerd met `Suspense` en de `use`-API vindt het laden van data plaats op de achtergrond. Wanneer `isActive` overschakelt naar `true`, verschijnt de inhoud direct zonder laadindicator. ## Activity en TanStack Query: de cache-valkuil Een veelvoorkomende fout met `` betreft [TanStack Query](https://tanstack.com/query/latest). Aangezien `useQuery` intern afhankelijk is van `useEffect`, worden queries binnen een verborgen `` niet uitgevoerd \u2014 het effect is gedemount. ```tsx // components/UserDashboard.tsx // Probleem: useQuery haalt geen data op wanneer Activity verborgen is import { useQuery, useQueryClient } from '@tanstack/react-query' import { Activity } from 'react' function UserStats() { // Deze useQuery zal NIET draaien terwijl het verborgen is const { data } = useQuery({ queryKey: ['user-stats'], queryFn: fetchUserStats, }) return } // Oplossing: data prefetchen buiten de Activity-grens function DashboardWithPrefetch({ showStats }: { showStats: boolean }) { const queryClient = useQueryClient() // Prefetch op ouderniveau — draait ongeacht de Activity-modus queryClient.ensureQueryData({ queryKey: ['user-stats'], queryFn: fetchUserStats, }) return ( ) } ``` De oplossing is eenvoudig: verplaats het prefetchen van data naar een oudercomponent of gebruik `queryClient.ensureQueryData` buiten de ``-grens. De gecachte data is beschikbaar zodra het verborgen component weer zichtbaar wordt. > **Geheugenafweging** > > Activity ruilt geheugen voor snelheid. Elke verborgen componentenboom blijft in het geheugen met zijn volledige DOM. Bij applicaties met veel verborgen routes is het raadzaam het geheugengebruik te monitoren. Het React-team onderzoekt automatische verwijdering van de minst recent gebruikte verborgen Activities in toekomstige versies. ## useEffectEvent en Activity combineren Beide API's vullen elkaar aan in realistische navigatiepatronen. Een veelvoorkomend scenario: een dashboard met tabbladen waarbij elk tabblad WebSocket-subscriptions en analytics-tracking heeft. ```tsx // components/LiveDashboard.tsx import { useEffect, useEffectEvent, useState } from 'react' import { Activity } from 'react' function LiveFeed({ channel, userId }: { channel: string; userId: string }) { const [messages, setMessages] = useState([]) // Analytics-tracking met huidige userId — geen effect-resync 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]) // Schone herverbinding alleen bij kanaaalwissel return } export function LiveDashboard({ userId }: { userId: string }) { const [activeChannel, setActiveChannel] = useState('general') const channels = ['general', 'alerts', 'metrics'] return (
{channels.map((ch) => ( ))}
) } ``` Bij het wisselen van kanaal wordt de WebSocket-verbinding van de verborgen feed verbroken (effect-cleanup via ``). De berichtengeschiedenis blijft behouden in de state. Het `onNewMessage` Effect Event zorgt ervoor dat analytics altijd naar de huidige `userId` verwijzen zonder WebSocket-herverbindingen af te dwingen. ## Sollicitatievragen: useEffectEvent en Activity Deze vragen toetsen het begrip van de nieuwe API's en hun interactie met het renderingmodel van React. Ze komen steeds vaker voor in [React-sollicitatiegesprekken](/technologies/react-next/interview-questions/react-hooks) bij bedrijven die React 19.2 gebruiken. **V1: Welk probleem lost useEffectEvent op dat useCallback niet kan oplossen?** `useCallback` cre\u00ebert een gememoriseerde functie, maar die moet nog steeds in de dependency array van het effect worden opgenomen. Als een van de eigen dependencies verandert, verandert de callback, wat het effect opnieuw activeert. `useEffectEvent` cre\u00ebert een functie die altijd de meest recente waarden leest zonder een dependency te zijn \u2014 het effect wordt er nooit door opnieuw uitgevoerd. Deze scheiding is met `useCallback` alleen onmogelijk. **V2: Kan een Effect Event als prop aan een child-component worden doorgegeven?** Nee. Effect Events zijn ontworpen om uitsluitend vanuit `useEffect` of andere Effect Events te worden aangeroepen. Hun identiteit verandert bij elke render, dus het doorgeven als props zou onnodige re-renders veroorzaken en het mentale model doorbreken. De [ESLint-plugin handhaaft deze regel](https://react.dev/reference/react/useEffectEvent). **V3: Hoe verschilt Activity van conditioneel rendering en CSS display:none?** Conditioneel rendering (`{show && }`) demount het component volledig \u2014 de state wordt vernietigd. CSS `display: none` verbergt visueel maar houdt alle effects draaiend, wat resources verspilt. `` behoudt de state, ruimt effects op, stelt updates uit naar idle-prioriteit en kan content op de achtergrond pre-renderen. **V4: Wat gebeurt er met useEffect binnen een verborgen Activity?** Wanneer een `` overschakelt naar `mode="hidden"`, voert React alle cleanup-functies van effects uit (de retourwaarde van `useEffect`). Geen nieuwe effects worden gemount zolang het component verborgen is. Wanneer het weer zichtbaar wordt, worden effects opnieuw gemount met de behouden state. Daarom hebben [data fetching-bibliotheken](/technologies/react-next/interview-questions/react-query-tanstack) die op `useEffect` vertrouwen prefetching-strategie\u00ebn nodig buiten de Activity-grens. **V5: Hoe kan een route worden ge-pre-renderd met Activity en Suspense?** De route wordt gewrapped in `` met een ``-boundary erbinnen. Voor het laden van data wordt de `use()`-API of een Suspense-compatibele databron gebruikt. React rendert de verborgen boom op lage prioriteit en lost de Suspense-boundary op de achtergrond op. Wanneer de gebruiker navigeert en `mode` overschakelt naar `"visible"`, verschijnt de volledig gerenderde content direct zonder laadstatus. **V6: Is useEffectEvent een vervanging voor de exhaustive-deps lint-regel?** Nee. De [exhaustive-deps-regel](https://react.dev/reference/react/useEffect) blijft essentieel voor het opsporen van daadwerkelijk ontbrekende dependencies. `useEffectEvent` behandelt een specifiek geval: logica die reactieve waarden leest maar niet mag bepalen wanneer het effect opnieuw draait (analytics, notificaties, logging). Het gebruiken om alle dependency-waarschuwingen te onderdrukken verbergt bugs en ondermijnt het doel. ## Conclusie - `useEffectEvent` vervangt de `useRef`-workaround voor stale closures in effects, met native linter-ondersteuning in `eslint-plugin-react-hooks@6+` - Effect Events lezen altijd de meest recente props en state zonder effect-resynchronisatie te activeren \u2014 ideaal voor analytics, logging en notificatie-callbacks - `` behoudt component-state terwijl effects worden opgeruimd, als middenweg tussen conditioneel rendering en CSS-verberging - Verborgen Activities pre-renderen op idle-prioriteit, wat directe navigatie mogelijk maakt in combinatie met `Suspense` en de `use`-API - [TanStack Query](/technologies/react-next/interview-questions/react-query-tanstack) en andere effect-gebaseerde bibliotheken hebben prefetching nodig buiten Activity-grenzen, aangezien `useEffect` niet draait in verborgen modus - Beide API's zijn beschikbaar vanaf React 19.2 \u2014 ESLint en React samen updaten voor volledige tooling-ondersteuning --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/nl/blog/react-next/react-19-useeffectevent-activity-new-apis-interview-questions