English flagEnglish

7a. gyakorlat – useState és useEffect

2026-04-12 8 perc olvasási idő GitHub

Bevezetés

Az előző órákon megismertük a React alapjait: komponensek, JSX, props és TypeScript típusozás. A 6. gyakorlatban egy statikus HTML prototípust alakítottunk React komponensekké. Az oldalunk már strukturált — de statikus. Ha rákattintunk egy gombra, semmi nem változik.

A mai óra ezt oldja meg. Két hook-ot tanulunk meg, amelyek nélkül egyetlen valódi React alkalmazás sem létezik:

  • useState – hogyan tárolunk és változtatunk adatot egy komponensen belül
  • useEffect – hogyan kezeljük a mellékhatásokat (adatbetöltés, időzítők, eseményfigyelők)

A mai óra után képes leszel:

  • useState-tel állapotot létrehozni és biztonságosan módosítani
  • Megérteni, mikor és miért renderelődik újra egy komponens
  • useEffect-et megfelelő dependency array-jel írni
  • Cleanup függvénnyel memóriaszivárgást elkerülni

A projekt megnyitása

A mai gyakorlat a sandbox-react-handout projektet használja.

cd gyak7v2/sandbox-react-handout
npm install
npm run dev

Nyisd meg a src/App.tsx fájlt. Az alkalmazás hét kis kártyára van tördelve — minden kártya egy önálló, izolált kísérlet. A fájl tele van TODO kommentekkel: ezeket kell életre kelteni.

ℹ️

Tartsd nyitva a böngésző fejlesztői konzolját (F12 → Console) az egész gyakorlat alatt. A useEffect feladatoknál a konzol kimenet a tananyag szerves része.


Mi az a hook?

Mielőtt belevágunk, egy perc elmélet.

A hookokat a React 16.8-ban vezette be. Olyan speciális függvények, amelyek React-specifikus funkcionalitást adnak a funkcionális komponenseknek: állapot, mellékhatás, kontextus stb.

Két szabály, amelyeket mindig be kell tartani:

  1. Hookokat csak komponens felső szintjén hívj meg — nem feltétel belsejében, nem ciklusban.
  2. Hookokat csak React komponensből hívj meg — sima JavaScript függvényből nem.

A nevük mindig use-zal kezdődik: useState, useEffect, useParams, useNavigate. Ezeket fogod látni ma és a következő gyakorlatokon is.


useState – állapot kezelése

Az állapot fogalma

Képzeld el, hogy az oldalon van egy számláló, amelynek értéke 0. Megnyomom a „+” gombot — az értéknek 1-re kell változnia. De honnan tudja a React, hogy frissíteni kell az oldalt?

Egy közönséges JavaScript változó (let ertek = 0) nem elegendő: ha megváltoztatod, a React nem értesül róla, és az oldal nem frissül.

Erre való az állapot (state). Az állapot olyan adat, amely:

  • a komponenshez tartozik,
  • idővel megváltozhat,
  • és ha változik, a React automatikusan újrarendereli a komponenst.

Szintaxis

const [ertek, setErtek] = useState(kezdoErtek)
RészSzerepe
ertekAz aktuális értéket tárolja — ezt olvasod a JSX-ben
setErtekSetter függvény — ezzel módosítod az értéket
kezdoErtekAz érték, amivel a komponens először jelenik meg

Ez array destructuring: a useState egy kételemű tömböt ad vissza, és mi egyszerre adjuk meg mindkét elem nevét.

⚠️

Soha ne módosítsd az állapotot közvetlenül! Az ertek = 5 egyszerűen egy JavaScript értékadás — a React nem értesül a változásról, az oldal nem frissül. Mindig a setter függvényt (setErtek(5)) használd.

Az import a fájl elejére:

import { useState } from 'react'

1. feladat: Számláló

Cél: A „+” gomb növelje a számot, a „-” csökkentse.

1. lépés – Állapot létrehozása

A Szamlalo függvényen belül, a return elé:

const [count, setCount] = useState(0)

0 a kezdőérték — nulláról indul.

2. lépés – Az érték megjelenítése

A JSX-ben cseréld le a hardcoded számot a state változóra:

<p className="value">{count}</p>

A kapcsos zárójel JSX-ben JavaScript kifejezést jelent. Mentés után a böngészőben ugyanúgy 0 látszik, de most az állapotból jön — nem hardcodeolva.

3. lépés – Gombok bekötése

<button onClick={() => setCount(count - 1)}>-</button>
<button onClick={() => setCount(count + 1)}>+</button>

Az onClick egy nyílfüggvényt kap. Azért kell nyílfüggvénybe csomagolni, mert különben a setCount(count + 1) az oldal betöltésekor azonnal lefutna — nem a kattintásra várna.

ℹ️

Amikor a setCount-ot hívod, a React újrarendereli a Szamlalo komponenst — lényegében újra meghívja a függvényt az új count értékkel. Ezért jelenik meg a friss szám.

Kísérletezz:

  • Változtasd a kezdőértéket useState(10)-re — onnan indul a számláló
  • Adj hozzá egy „Reset” gombot: onClick={() => setCount(0)}

2. feladat: Be/Ki kapcsoló

Cél: Egy gomb megmutat vagy elrejt egy szöveget. A gomb felirata tükrözze az aktuális állapotot.

1. lépés – Boolean állapot

const [visible, setVisible] = useState(false)

false = kezdetben rejtett.

2. lépés – Gomb felirata és eseménykezelő

<button onClick={() => setVisible(!visible)}>
  {visible ? 'Elrejt' : 'Megmutat'}
</button>

A !visible megfordítja az értéket (toggle). A ternary operátor (? :) a két lehetséges felirat közül a megfelelőt adja vissza.

3. lépés – Feltételes megjelenítés

{visible && <p className="message">Helló! Látható vagyok!</p>}

A && operátor rövidre zárja a kiértékelést: ha a bal oldal hamis, a jobb oldal nem kerül renderelésre — a DOM-ban sem jelenik meg az elem. Ez a leggyakoribb React minta feltételes renderelésre.

Kísérletezz:

  • Változtasd a kezdőértéket true-ra — betöltéskor a szöveg már látható lesz
  • A && helyett próbálj ternary-t: {visible ? <p>Látom</p> : null}

3. feladat: Kontrollált input

Cél: Szövegmező karakterszámlálóval és beírt szöveg megjelenítésével, karakterlimittel.

Mi a különbség kontrollált és nem kontrollált input között?

UncontrolledControlled
Az értéket tároljaDOM (az input maga)React state
Olvasásref-en keresztülközvetlenül a state-ből
Validálás, limitNehézkesEgyszerű
Ajánlott?RitkánÁltalában igen

1. lépés – String állapot

const [text, setText] = useState('')

2. lépés – Input bekötése

<input
  type="text"
  value={text}
  onChange={(e) => setText(e.target.value)}
  placeholder="Írj valamit..."
/>
  • value={text} — az input értéke mindig a state tükre
  • onChange — minden billentyűleütéskor frissíti a state-et; e.target.value az aktuális szöveg

3. lépés – Karakterszámláló

<p className="hint">
  {text.length} / {limit} karakter
</p>

4. lépés – Szöveg megjelenítése

{text && <p className="message">Beírtad: „{text}"</p>}

Kísérletezz:

  • Ha text.length >= limit, adj hozzá extra CSS osztályt, ami pirossá teszi a számlálót
  • A limit értékét csökkentsd 10-re — hamar eléri

useEffect – mellékhatások kezelése

Mi a mellékhatás?

Egy React komponens fő feladata: adatokat kap (state, props) → JSX-et ad vissza. Ennyi. De egy valódi alkalmazásban szükség van mellékhatásokra is:

  • Adatok betöltése API-ból
  • Időzítők (setTimeout, setInterval)
  • Konzolba írás, eseményfigyelők feliratkozása/leiratkozása

Ezek kívül esnek a tiszta „adat → JSX” folyamon. A useEffect arra való, hogy ezeket a renderelési cikluson kívül, kontrolláltan végezzük.

Szintaxis

useEffect(() => {
  // mellékhatás kódja

  return () => {
    // cleanup függvény (opcionális)
  }
}, [/* dependency array */])

A dependency array

Ez a useEffect kulcsa — ez határozza meg, mikor fusson az effect:

Dependency arrayMikor fut?
Nincs megadvaMinden render után
[] üres tömbCsak egyszer, mountoláskor
[a, b]Ha a vagy b megváltozott az előző render óta
⚠️

Ha egy useEffect-en belül használt változó nincs a dependency array-ben, az ESLint figyelmeztetni fog. Ne hagyd figyelmen kívül — a hiányzó függőség nehezen debugolható hibákhoz vezet (a hook régi, „befagyott” értékeket lát).

Az import bővítése:

import { useState, useEffect } from 'react'

4. feladat: Minden renderkor lefut

Cél: Megfigyelni a dependency array nélküli viselkedést — és megérteni, miért kerüljük.

State-ek:

const [count, setCount] = useState(0)
const [text, setText] = useState('')

Effect — dependency array nélkül:

useEffect(() => {
  console.log('Effect lefutott – count:', count, '| text:', text)
}) // nincs dependency array!

JSX:

<button onClick={() => setCount(count + 1)}>Kattints ({count})</button>
<input value={text} onChange={(e) => setText(e.target.value)} placeholder="Gépelj..." />

Teszteld: Kattints a gombra, gépelj a mezőbe — minden interakció után új sor jelenik meg a konzolban. Az effect minden render után lefut, bármelyik state változik is.

Miért kerüljük? Ha az effect API-hívást végez, minden billentyűleütésre hálózati kérés indulna. Dependency array nélküli useEffect szinte mindig hibás kód.


5. feladat: Csak mountoláskor fut le

Cél: API-hívás szimulálása — az effect csak egyszer, a komponens megjelenésekor fut le.

Mountolás = a komponens először jelenik meg az oldalon. Ellentéte: unmountolás = a komponens eltűnik az oldalról.

State-ek:

const [adat, setAdat] = useState<string | null>(null)
const [tolt, setTolt] = useState(true)

A string | null TypeScript típus: kezdetben null (nincs adat), betöltés után string lesz.

Effect üres [] array-jel:

useEffect(() => {
  const timer = setTimeout(() => {
    setAdat('Betöltött adat: React 19')
    setTolt(false)
  }, 1500)

  return () => clearTimeout(timer) // cleanup
}, []) // üres []: csak mountoláskor fut

Feltételes megjelenítés:

{tolt ? <p className="hint">Betöltés...</p> : <p className="message">{adat}</p>}

Kísérletezz:

  • Változtasd 1500-at 3000-re — lassabb betöltés
  • Vedd ki a []-t — mi történik? Minden render újabb setTimeout-ot indít, ami újabb rendert okoz, ami újabb timert indít → végtelen ciklus!

6. feladat: State figyelése

Cél: Automatikusan frissíteni egy derived értéket, valahányszor egy másik state megváltozik.

State-ek:

const [count, setCount] = useState(0)
const [paros, setParos] = useState(true)

Effect [count] dependency-vel:

useEffect(() => {
  setParos(count % 2 === 0)
}, [count]) // csak count változásakor fut

JSX:

<p className="value">{count}</p>
<button onClick={() => setCount(count + 1)}>Növel</button>
<p className="message">{paros ? 'Páros' : 'Páratlan'}</p>
ℹ️

Ebben a konkrét esetben a useEffect valójában felesleges — a paros értékét közvetlenül ki lehetne számítani a renderből: const paros = count % 2 === 0. A useEffect mellékhatásokra való, nem értékek kiszámítására. Ez a példa csupán a dependency array viselkedését mutatja be.

Kísérletezz:

  • Cseréld []-re a deps arrayt — az effect csak mountoláskor fut, a paros soha nem frissül
  • Vedd ki teljesen a deps arrayt — setParos újra rendert okoz, ami újra futtatja az effectet → végtelen ciklus! Próbáld ki, mielőtt visszarakod.

7. feladat: Cleanup – időzítő leállítása

Cél: Start/stop számlálót írni. Ez a feladat a Idozito komponensbe kerül — ennek most csak az üres váza van, a megvalósítás a te feladatod.

Mi a cleanup?

Az useEffect visszatérhet egy cleanup függvénnyel. Ez lefut:

  • mielőtt az effect újra lefutna (ha a deps megváltoztak),
  • amikor a komponens unmountolódik.

Ha elindítasz egy setInterval-t és nem állítod le, az a háttérben tovább fut akkor is, ha a komponens már régen eltűnt az oldalról. Ez memóriaszivárgást és kiszámíthatatlan viselkedést okoz.

State-ek:

const [masodperc, setMasodperc] = useState(0)
const [fut, setFut] = useState(false)

Effect cleanup-pal:

useEffect(() => {
  if (!fut) return // ha nem fut, ne indítson intervalt

  const interval = setInterval(() => {
    setMasodperc((s) => s + 1) // funkcionális update!
  }, 1000)

  return () => clearInterval(interval) // cleanup
}, [fut])
⚠️

Figyeld meg a setMasodperc((s) => s + 1) szerkezetet — ez a funkcionális update. A setInterval callback bezáródik az eredeti masodperc értékére. Ha masodperc + 1-et írnánk, a callback mindig az induláskori 0-t látná, és a számláló soha nem menne 1 fölé.

JSX:

<p className="value">{masodperc}s</p>
<div className="row">
  <button onClick={() => setFut(!fut)}>
    {fut ? 'Megállít' : 'Elindít'}
  </button>
  <button onClick={() => { setFut(false); setMasodperc(0) }}>
    Visszaállít
  </button>
</div>

A cleanup pontos folyamata:

  1. fut = false → kattintasz az „Elindít” gombra
  2. fut true-ra vált → React újrarenderelés
  3. useEffect lefut → setInterval elindul
  4. Kattintasz a „Megállít” gombra → fut false-ra vált
  5. Cleanup lefutclearInterval(interval) leállítja az intervalt
  6. useEffect újra lefut → if (!fut) return → azonnal kilép

Kísérletezz:

  • Töröld ki a return () => clearInterval(interval) sort. Indítsd el, állítsd meg, indítsd újra — a számláló egyre gyorsabban nő (minden indításnál egy új interval adódik hozzá, a régiek megmaradnak)
  • Próbáld (s) => s + 1 helyett masodperc + 1-re cserélni — mi változik?

Összefoglalás

useState

const [ertek, setErtek] = useState(kezdoErtek)
SzabályMiért?
Mindig setterrel módosítsdKülönben React nem értesül a változásról
Funkcionális update: set(x => x + 1)Ha az új érték az előzőtől függ (pl. interval, aszinkron)
Komponens felső szintjén hívd megHookokat nem hívhatsz feltételek vagy ciklusok belsejében

useEffect

Dependency arrayMikor fut?
Nincs megadvaMinden render után — szinte mindig kerüld
[]Csak mountoláskor
[a, b]Ha a vagy b megváltozott
return () => ...Cleanup: következő effect előtt + unmountoláskor

Tipikus hibák

JelenségValószínű okMegoldás
Kattintás után nem frissül az oldalKözvetlen értékadás (ertek = 5)Használj setter függvényt
Végtelen újrarenderelésHiányzó vagy felesleges depsEllenőrizd az ESLint figyelmeztetéseket
Interval leállíthatatlanHiányzó cleanupreturn () => clearInterval(interval)
Számláló mindig 1-en állBezárt régi érték a callbackbenFunkcionális update: set(s => s + 1)

A következő gyakorlatban (7b. gyakorlat) ugyanezeket az eszközöket egy valódi webshopban fogjuk alkalmazni: React Router, prop drilling, kontrollált formok és custom hook.