DEV Community

Cover image for Detaylı React Hooks Kullanımı: useEffect
Masum Gökyüz
Masum Gökyüz

Posted on

Detaylı React Hooks Kullanımı: useEffect

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 effecttir. 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>
  );
}


Enter fullscreen mode Exit fullscreen mode

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>
  );
}

Enter fullscreen mode Exit fullscreen mode

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>)

Enter fullscreen mode Exit fullscreen mode

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');
}, []);

Enter fullscreen mode Exit fullscreen mode

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]);

Enter fullscreen mode Exit fullscreen mode

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;

Enter fullscreen mode Exit fullscreen mode

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

Enter fullscreen mode Exit fullscreen mode

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]);

Enter fullscreen mode Exit fullscreen mode

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;

Enter fullscreen mode Exit fullscreen mode

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;

Enter fullscreen mode Exit fullscreen mode

Aslında gerçekleşecek işlemleri tahmin etmek çok basit:

  1. section state'inin initial state'i posts olarak belirlendiğinden, ilk render edildiğinde useEffect hook'u çalışır ve API'dan posts 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.

  2. Posts dışındaki bir butona tıklandığında, meselâ Users butonuna tıklandığında section state'i users değerini alır ve useEffect'in sahip olduğu dependency array'i tetiklenerek, useEffect hook'unu devreye sokar. section artık users 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.

  3. İ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;

Enter fullscreen mode Exit fullscreen mode
  1. İlk olarak items adında array alan bir state oluşturduk ve gelen verileri items array'inde depolamak için fetch işleminin içerisinde gelen verileri setItems fonksiyonuyla JSON'a çevirerek items array'ine depoladık.

  2. Daha sonra items array'ini mapleyerek ekrana yazdırdık.

  3. dependency array'inde section state'i olduğu için, section state'i değiştiğinde useEffect hook'u çalışır ve API'dan verileri çeker. Bu sayede, Posts butonuna tıkladığımızda posts verilerini, Users butonuna tıkladığımızda users verilerini, Comments butonuna tıkladığımızda comments verilerini çekmiş oluruz. Çekilen verileri de son olarak map 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;

Enter fullscreen mode Exit fullscreen mode
  1. İlk olarak windowWidth adında bir state tanımladık ve bu state'in initial state'ini pencerenin iç genişliği (window.innerWidth) olarak belirledik.

  2. 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;

Enter fullscreen mode Exit fullscreen mode

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;

Enter fullscreen mode Exit fullscreen mode

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')
  }
})

Enter fullscreen mode Exit fullscreen mode

Top comments (0)