Waarom heb je useEffect nodig?

Stel je wilt iets doen zodra je component wordt geladen. Je zou denken: "Ik schrijf gewoon code in mijn component!"

Dit gaat fout (infinite loop!):

const Counter = () => {
  const [count, setCount] = useState(0);
  
  // Dit zorgt voor een infinite loop!
  setCount(count + 1);
  
  return <div>Count: {count}</div>;
};

Probleem: Elke keer dat je setCount aanroept, rendert het component opnieuw. Bij elke render wordt setCount weer aangeroepen → infinite loop!

Wat is useEffect?

useEffect is een React Hook die ervoor zorgt dat code wordt uitgevoerd op het juiste moment - niet bij elke render.

Wanneer gebruik je useEffect?

  • Data ophalen van een API zodra component laadt
  • De pagina titel veranderen
  • Een timer starten
  • Iets in de console loggen bij het laden

useEffect importeren

import { useState, useEffect } from "react";

Basis Syntax

useEffect(() => {
  // Code die je wilt uitvoeren
  console.log('Component is geladen!');
}, []); // ← Dependency array

useEffect heeft 2 dingen:

  1. Een functie - de code die je wilt uitvoeren
  2. Een dependency array - wanneer de code moet worden uitgevoerd

Dependency Array

De dependency array [] bepaalt wanneer useEffect wordt uitgevoerd:

1. Lege Array [] - Eén keer bij laden

useEffect(() => {
  console.log('Component geladen!');
}, []); // Voer 1x uit bij mount

Gebruik dit voor: Data fetchen, timers starten, document title aanpassen

2. Met Dependencies - Bij verandering

const [count, setCount] = useState(0);

useEffect(() => {
  console.log('Count veranderde naar:', count);
}, [count]); // Voer uit elke keer als count verandert

3. Geen Array - Bij elke render

useEffect(() => {
  console.log('Component renderde opnieuw');
}); // Voer uit bij elke render (meestal niet nodig!)

Let op: Zonder dependency array wordt useEffect bij elke render uitgevoerd. Dit wil je bijna nooit!

Zie het in actie — drie useEffects naast elkaar

Je hebt nu de drie patronen gelezen. Hieronder zie je ze tegelijk werken, elk met een écht zichtbaar gevolg. Klik op de + of typ in naam en kijk wat er gebeurt:

0
Render #1
1
Logt elke render
Draait na elke render. Print een logregel.
useEffect(() => {
  console.log('render!');
}); // geen array
Console output:
leeg...
2
Laadt user bij mount
Draait één keer bij de eerste render. Perfect voor een API-call.
useEffect(() => {
  fetchUser().then(setUser);
}, []); // lege array
User data:
leeg...
3
Update titel bij count
Draait als count verandert. Niet als naam verandert.
useEffect(() => {
  document.title = `(${count}) App`;
}, [count]); // [count]
Browser tab:
App

Probeer dit en kijk goed:

  1. Klik op +. Effect 1 logt, Effect 3 update de tab, Effect 2 doet niks.
  2. Typ iets in naam. Effect 1 logt, Effect 2 doet niks, Effect 3 doet ook niks (count is niet veranderd).
  3. Effect 2 zie je maar één keer — bij het laden. Daarna nooit meer.

De gouden regel: wat in de dep-array staat = waar de effect naar luistert. Wat erbuiten staat = wordt genegeerd.

Praktische Voorbeelden

Document Title Aanpassen

import { useState, useEffect } from 'react';

const Counter = () => {
  const [count, setCount] = useState(0);

  useEffect(() => {
    document.title = `Count: ${count}`;
  }, [count]); // Update title als count verandert

  return (
    <div>
      <p>{count}</p>
      <button onClick={() => setCount(count + 1)}>+1</button>
    </div>
  );
};

Console Log bij Laden

useEffect(() => {
  console.log('Component is gemount!');
}, []); // Voer 1x uit bij laden

Data Fetchen (basis)

import { useState, useEffect } from 'react';

const Users = () => {
  const [users, setUsers] = useState([]);

  useEffect(() => {
    // Fetch data bij laden
    fetch('https://api.example.com/users')
      .then(res => res.json())
      .then(data => setUsers(data));
  }, []); // Lege array = 1x uitvoeren

  return (
    <ul>
      {users.map(user => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  );
};

Cleanup Function

Soms moet je opruimen (cleanup) als component unmount. Bijvoorbeeld bij timers of event listeners:

Timer Voorbeeld

import { useState, useEffect } from 'react';

const Timer = () => {
  const [seconds, setSeconds] = useState(0);

  useEffect(() => {
    // Start timer
    const interval = setInterval(() => {
      setSeconds(s => s + 1);
    }, 1000);

    // Cleanup: stop timer bij unmount
    return () => {
      clearInterval(interval);
    };
  }, []); // Lege array = 1x uitvoeren

  return <div>Seconds: {seconds}</div>;
};

Cleanup function:

De functie die je return in useEffect wordt uitgevoerd als het component unmount. Gebruik dit om timers te stoppen, event listeners te verwijderen, etc.

Belangrijke Regels

  • Altijd dependency array gebruiken - Anders elke render!
  • Lege array [] voor "run once" - Bij component mount
  • Dependencies toevoegen - Als je state/props gebruikt in useEffect
  • Cleanup bij timers/listeners - Return functie voor cleanup

Veelgemaakte Fouten

Fout - Geen dependency array:

useEffect(() => {
  setCount(count + 1);  // Infinite loop!
}); // Geen array = elke render!

Goed - Met lege array:

useEffect(() => {
  // Voer 1x uit
}, []); // Lege array = mount only

Fout - Missing dependency:

useEffect(() => {
  console.log(count);
}, []); // count ontbreekt in array!

Goed - Dependency toegevoegd:

useEffect(() => {
  console.log(count);
}, [count]); // count is toegevoegd

Fout - Timer niet opruimen:

useEffect(() => {
  setInterval(() => {
    console.log('tick');
  }, 1000);
  // Geen cleanup = memory leak!
}, []);

Goed - Met cleanup:

useEffect(() => {
  const id = setInterval(() => {
    console.log('tick');
  }, 1000);
  
  return () => clearInterval(id); // Cleanup!
}, []);