useEffect
hook'u, bileşenler içerisinde side effects
işlemi yapmamıza olanak tanıyan bir hook'tur. Peki burada side effects
ile ne kastediyoruz? React'te side effects
DOM'u değiştirmek, network isteklerinde bulunmak veya global state'i güncellemek gibi bir bileşenin kapsamı dışındaki bir şeyi etkileyen herhangi bir davranış veya eylem anlamına gelir. Side effects
, bir bileşenin render aşaması sırasında gerçekleştirilemeyen, bunun yerine bileşen render edildikten sonra yürütülmesi gereken işlemlerdir.
Bir API'dan veri çekmek buna bir örnektir. Diyelim ki bir API'dan bazı verileri almak ve componentinizde görüntülemek istiyorsunuz. Bir network isteğinde bulunmak, yani request atmak componentin kapsamı dışında bir işlem içerdiği için esasen bir side effect
tir. Bu eylemi gerçekleştirmek için genellikle useEffect
hook'u kullanılır:
import React, { useState, useEffect } from 'react';
function FetchDataComponent() {
const [data, setData] = useState([]);
useEffect(() => {
fetch('https://example.com/api/data')
.then(response => response.json())
.then(data => setData(data))
.catch(error => console.error(error));
}, []);
return (
<div>
{data.map(item => (
<p key={item.id}>{item.name}</p>
))}
</div>
);
}
Yukarıdaki örnekte, bir requst atmak ve componentin state'ini alınan verilerle güncellemek için useEffect
hook'unu kullanıyoruz. useEffect
'e ikinci argüman olarak boş bir bağımlılık array'i ([]) geçerek, useEffect
'in component monte edildikten sonra yalnızca bir kez yürütülmesini sağlıyoruz. Eğer useEffect
kullanmasaydık, request atma her render işleminde yapılırdı, ki bu verimsiz olurdu ve performans sorunlarına yol açabilirdi.
Diğer side effects
örnekleri arasında tarayıcı geçmişini manipüle etmek, window nesnesine erişmek veya harici bir olaya abone olmak/bir olayı takip etmek/ (subscribe) yer alır. useEffect
hook'u, componentlerinizde bu işlemleri güvenli ve verimli bir şekilde gerçekleştirmenizi sağlar. Yeri gelmişken, harici (external) bir olaya abone olmanın ne olduğunu da bir örnek üzerinden anlatalım:
import { useState, useEffect } from 'react';
function RealtimeDataDisplay() {
const [data, setData] = useState([]);
useEffect(() => {
const interval = setInterval(() => {
fetch('https://example.com/data-feed')
.then(response => response.json())
.then(newData => setData(data => [...data, newData]));
}, 1000);
return () => {
clearInterval(interval);
};
}, []);
return (
<div>
{data.map(item => (
<p key={item.id}>{item.value}</p>
))}
</div>
);
}
Bu örnekte useEffect
hook'u, setInterval
metodunu kullanarak, her saniye https://example.com/data-feed URL'sine bir istekte bulunarak bir veri akışına abone olur. Veri alındığında, setData
fonksiyonu kullanılarak componentin state'ine eklenir (yeni veriler varsa tabii...).
useEffect
Hook'unu Yalnızca İlk Render'da Çalıştırmak
useEffect
hook'unun kullanımına geçmeden önce bu hook'un sentaksını, yani biçimini/formunu vermek iyi olacaktır. useEffect
hook'u iki argüman alır (ikincisi opsiyoneldir):
useEffect(<function>, <dependency>)
Yani, useEffect
hook'undan sonra bir fonksiyon çağırırız ve bu useEffect
'in çalışması da bir şeye bağlanır (dependency). Buradaki dependency
array biçiminde gösterilir. Eğer array boş ise, hook yalnızca ilk renderda çağrılır. Yani,
useEffect(() => {
console.log('Hello');
}, []);
Yukarıdaki örnekte, useEffect
hook'u sadece bir kez çalışır ve Hello
yazdırır. Eğer dependency
array'i boş değilse, useEffect
hook'u, dependency
array'inin içindeki herhangi bir değer değiştiğinde çalışır. Yani,
useEffect(() => {
console.log('Hello');
}, [name]);
Yukarıdaki örnekte, useEffect
hook'u sadece (sözgelimi) name
adındaki state
değiştiğinde çalışır. Eğer dependency
array'i verilmezse, useEffect
hook'u yalnızca ilk renderda çalışır.
useEffect
Hook'unu Her Render'da Çalıştırmak
Yukarıdaki örneklerde useEffect
hook'u yalnızca ilk renderda çalışıyordu ve daha sonrasında duruyordu - eğer dependency
array boş ise. Peki biz useEffect
hook'unu her render işlemi gerçekleştiğinde çalıştırmak istiyorsak ne yapmalıyız? Çok basit: dependency
array'ini kaldıracağız! Yani,
import React, { useState, useEffect } from 'react';
const App = () => {
const [count, setCount] = useState(0);
useEffect(() => {
console.log('I am called');
});
return (
<>
<h1>Count: {count}</h1>
<button onClick={() => setCount(count + 1)}>Increment</button>
</>
)
}
export default App;
Yukarıdaki örnekte her butona tıklandığında count
state'inin değeri artar ve count
state'i yeniden render edilir. Bu yeniden render edilme işleminde useEffect
hook'u devreye girer ve konsola I am called
yazdırır.
Kaldığımız yerden devam edelim ve useEffect
hook'unu daha detaylı bir şekilde ele alalım. Bunun için örneklerden yola çıkacağız.
Elimizde aşağıdaki gibi bir kod olsun:
import React, { useState } from 'react'
const App = () => {
const [section, setSection] = useState('posts');
useEffect(() => {
console.log('Hello');
}, []);
return (
<>
<div>
<button onClick={() => setSection('posts')}>Posts</button>
<button onClick={() => setSection('users')}>Users</button>
<button onClick={() => setSection('comments')}>Comments</button>
</div>
<h1> {section} </h1>
</>
)
}
export default App
Bu kodda, section
adında bir state
tanımlıyoruz ve bu state'i değiştirmek için üç tane buton tanımlıyoruz. Bu butonlara tıklandığında, setSection
fonksiyonu ile section
state'i değişiyor. Bu değişiklik, section
state'inin değerini değiştiriyor ve bu da componentin tekrar render edilmesine neden oluyor. Fark edileceğiz üzere, useEffect
hook'unun dependency
array'ini boş bıraktık. Yani useEffect
hook'u, component ilk kez çalıştırıldığında yürürlüğe girecektir ve konsola Hello
yazdıracaktır. Butonlara tıklayıp section
state'inin değerini değiştirsek bile, useEffect
devreye girmeyecektir çünkü useEffect
hook'unun dependency
array'ini boş bıraktık. useEffect
hook'undan her section
state'inin değiştiği durumda konsola Section state'i değişti!
yazdırmasını isteyelim:
useEffect(() => {
console.log("Section state'i değişti!");
}, [section]);
Bu şekilde, useEffect
hook'u sadece section
state'i değiştiğinde çalışır. Her section
state'i değiştiğinde, meselâ butona tıklandığında ve section
state'i 'users'
değeri aldığında, useEffect
hook'u devreye girer ve konsola Section state'i değişti!
yazdırır. section
state'inin initial state
'ini, yani başlangıç hâli/durumu
'nu posts
olarak belirlemiştik. Eğer ki Posts
butonuna basarsak, konsola herhangi bir yazının yazılmadığını görürüz çünkü useEffect
hook'u, dependency
olarak section
state'ini aldığı için, section
state'i değişmediği sürece çalışmaz (çünkü state hâlâ initial state
ile aynıdır). Ancak diyelim ki Users
butonuna basarsak, konsola Section state'i değişti!
yazısı konsola yazılır çünkü section
state'i initial state
'den farklı bir değere (yani, users
değerine) sahip olmuştur.
Real-world scenario üzerinden bir örnek vermemiz gerekirse, bir {JSON}Placeholder API bulup onun üzerinden useEffect
hook'unu ele alalım. Biz örnek için https://jsonplaceholder.typicode.com/ adresindeki endpointleri kullanacağız. Aşağıdaki kodu inceleyelim:
import React, { useState, useEffect } from 'react';
const App = () => {
const [section, setSection] = useState('posts');
useEffect(() => {
fetch(`https://jsonplaceholder.typicode.com/${section}`)
.then((response) => response.json())
.then((json) => console.log(json));
}, []);
return (
<>
<div>
<button onClick={() => setSection('posts')}>Posts</button>
<button onClick={() => setSection('users')}>Users</button>
<button onClick={() => setSection('comments')}>Comments</button>
</div>
<h1> {section} </h1>
</>
);
};
export default App;
Yukarıdaki örnekte API endpointine section
state'inin aldığı değere göre GET isteğinde bulunan bir fetch, yani veri çekme işlemi yaptık. useEffect
hook'unun dependency
array'ini ise boş bıraktık. Bu, şu anlama geliyor: Sayfa ilk kez render edildiğinde API'dan verileri çek ve daha sonra bir şey yapma. Biz meselâ Users
veya Comments
butonuna tıklasaydık, section
state'i değişmesine rağmen API'a attığımız istek değişmeyecekti ve konsoldaki JSON verilerinde bir güncelleme, değişme, ekleme vb. olmayacaktı. Neden? Çünkü useEffect
hook'unun dependency
array'i section
state'inin değerine bağlı değildir. dependency
array'i section
state'i olarak belirlediğimizi düşünelim:
import React, { useState, useEffect } from 'react';
const App = () => {
const [section, setSection] = useState('posts');
useEffect(() => {
fetch(`https://jsonplaceholder.typicode.com/${section}`)
.then((response) => response.json())
.then((json) => console.log(json));
}, [section]);
return (
<>
<div>
<button onClick={() => setSection('posts')}>Posts</button>
<button onClick={() => setSection('users')}>Users</button>
<button onClick={() => setSection('comments')}>Comments</button>
</div>
<h1> {section} </h1>
</>
);
};
export default App;
Aslında gerçekleşecek işlemleri tahmin etmek çok basit:
section
state'inininitial state
'iposts
olarak belirlendiğinden, ilk render edildiğindeuseEffect
hook'u çalışır ve API'danposts
verilerini çeker. Yani, https://jsonplaceholder.typicode.com/**posts** adresine bir GET isteği atar ve gelen verileri JSON formatına çevirerek konsola yazdırır.Posts
dışındaki bir butona tıklandığında, meselâUsers
butonuna tıklandığındasection
state'iusers
değerini alır veuseEffect
'in sahip olduğudependency
array'i tetiklenerek,useEffect
hook'unu devreye sokar.section
artıkusers
değeri aldığı için,useEffect
hook'u https://jsonplaceholder.typicode.com/**users** adresine bir GET isteği atar ve bu endpoint'in bize sağladığı verileri JSON formatına çevirerek konsola yazdırır.İkinci adımdaki işlemin aynısı
Comments
butonuna tıklandığında da gerçekleşir.
Konsola yazdırmak yerine aynı işlemi yaparak gelen verileri ekrana yazdıralım. Bunun için gelen verileri bir array içerisinde tutan bir state'e ihtiyacımız olacaktır. Yani, aşağıdaki kod parçasını inceleyelim:
import React, { useState, useEffect } from 'react';
const App = () => {
const [section, setSection] = useState('posts');
const [items, setItems] = useState([]);
useEffect(() => {
fetch(`https://jsonplaceholder.typicode.com/${section}`)
.then((response) => response.json())
.then((json) => setItems(json));
}, [section]);
return (
<>
<div>
<button onClick={() => setSection('posts')}>Posts</button>
<button onClick={() => setSection('users')}>Users</button>
<button onClick={() => setSection('comments')}>Comments</button>
</div>
<h1> {section} </h1>
<h2>Çekilen Veriler</h2>
{items.map((item, i) => (
<pre key={i}>{JSON.stringify(item, null, 2)}</pre>
))}
</>
);
};
export default App;
İlk olarak
items
adında array alan bir state oluşturduk ve gelen verileriitems
array'inde depolamak içinfetch
işleminin içerisinde gelen verilerisetItems
fonksiyonuyla JSON'a çevirerekitems
array'ine depoladık.Daha sonra
items
array'ini mapleyerek ekrana yazdırdık.dependency
array'indesection
state'i olduğu için,section
state'i değiştiğindeuseEffect
hook'u çalışır ve API'dan verileri çeker. Bu sayede,Posts
butonuna tıkladığımızdaposts
verilerini,Users
butonuna tıkladığımızdausers
verilerini,Comments
butonuna tıkladığımızdacomments
verilerini çekmiş oluruz. Çekilen verileri de son olarakmap
ile ekrana yazdırırız.
Başka bir örnek daha yapalım. Anlık pencere genişliğini veren bir state tanımlayalım ve bu state'in değerini de ekrana yazdıralım:
import React, { useState } from 'react';
const App = () => {
const [windowWidth, setWindoWidth] = useState(window.innerWidth);
return (
<>
{windowWidth}
</>
);
};
export default App;
İlk olarak
windowWidth
adında bir state tanımladık ve bu state'ininitial state
'ini pencerenin iç genişliği (window.innerWidth) olarak belirledik.Daha sonra da
windowWidth
state'inin aldığı değeri ekrana yazdırdık.
Benim ekranım full olduğu için ekranda 1440
değerini görüyorum. Ancak bu statik bir değer. Browserımın genişliğini biraz küçültüp sayfayı yenilersem, bu değer farklı olacaktır. Ancak bunu sayfayı yenilemeden, dinamik bir şekilde yapabilir miyiz?
Aşağıdaki kodu inceleyelim:
import React, { useState, useEffect } from 'react';
const App = () => {
const [windowWidth, setWindoWidth] = useState(window.innerWidth);
const handleResize = () => {
setWindoWidth(window.innerWidth);
};
useEffect(() => {
window.addEventListener('resize', handleResize);
}, []);
return (
<>
{windowWidth}
</>
);
};
export default App;
Burada useEffect
hook'unu işin içerisine dahil edip bir EventListener
ekliyoruz ve ilk argüman olarak event türünü (buradaki resize
), ikinci argüman olarak da bu event tetiklendiğinde uygulanacak olan fonksiyonu yazıyoruz (burada handleResize
fonksiyonu). Bundan böyle her browser penceresinin genişliği değiştiğinde handleResize
fonksiyonu çalışacak ve windowWidth
state'i değişecek. Dolayısıyla da ekrana yazdırılan windowWidth
değeri değişecek.
NOT: Performans sorunlarıyla karşılaşmamak için eklediğimiz bu EventListener
'ı silmemiz iyi olacaktır:
import React, { useState, useEffect } from 'react';
const App = () => {
const [windowWidth, setWindoWidth] = useState(window.innerWidth);
const handleResize = () => {
setWindoWidth(window.innerWidth);
};
useEffect(() => {
window.addEventListener('resize', handleResize);
return () => {
window.removeEventListener('resize', handleResize);
}
}, []);
return (
<>
{windowWidth}
</>
);
};
export default App;
Burada useEffect
hook'unun içerisine bir return
ifadesi ekledik ve bu return
ifadesinin içerisinde EventListener
'ı sildik. Bu sayede, useEffect
hook'u çalıştırılmadan önce EventListener
'ı ekliyoruz ve useEffect
hook'u çalıştırıldıktan sonra da EventListener
'ı siliyoruz.
Bunu yapmak önemlidir çünkü EventListener
kaldırılmazsa, component unmount edildikten sonra bile değişiklikleri dinlemeye devam edecektir. Bu da uygulamanızda bellek sorunlarına ve beklenmedik davranışlara yol açabilir.
EventListener
'ı kaldıran bir fonksiyon return
ederek, componente artık ihtiyaç duyulmadığında EventListener
'ın temizlenmesini sağlarsınız.
Özetle, bir useEffect
hook'una bu tür durumlarda bir removeEventListener
eklemek, bellek sorunlarını önlemek ve uygulamanızın sorunsuz çalışmasını sağlamak için en iyi uygulamadır.
useEffect
içerisinde temizlik işlemleri yapmak isteyenler veya daha basit bir şekilde bu işlemin sentaksını, yani biçimini/formunu görmek isteyenler için aşağıya bu işlemin sentaksını ekliyorum:
useEffect(() => {
console.log('This is my side effect')
return () => {
console.log('This is my clean up')
}
})
Top comments (0)