useEffect, bir bileşeni harici bir sistem ile senkronize etmenizi sağlayan React Hook’udur.

useEffect(setup, dependencies?)

Referans

useEffect(setup, dependencies?)

Bir Effect bildirmek için bileşeninizin en üst düzeyinde useEffect’i çağırın:

import { useEffect } from 'react';
import { createConnection } from './chat.js';

function ChatRoom({ roomId }) {
const [serverUrl, setServerUrl] = useState('https://localhost:1234');

useEffect(() => {
const connection = createConnection(serverUrl, roomId);
connection.connect();
return () => {
connection.disconnect();
};
}, [serverUrl, roomId]);
// ...
}

Daha fazla örnek görmek için aşağıya bakınız.

Parametreler

  • setup: Effect’inizin mantığını içeren fonksiyon. Kurulum (setup) fonksiyonunuz isteğe bağlı olarak temizleme (cleanup) fonksiyonu da döndürebilir. Bileşeniniz DOM’a eklendiğinde, React kurulum fonksiyonunuzu çalıştıracaktır. Değişen bağımlılıklar ile her yeniden render işleminden sonra, React önce temizleme fonksiyonunu (eğer sağladıysanız) eski değerlerle çalıştıracak ve ardından kurulum fonksiyonunuzu yeni değerlerle çalıştıracaktır. Bileşeniniz DOM’dan kaldırıldıktan sonra, React temizleme fonksiyonunuzu çalıştıracaktır.

  • Opsiyonel bağımlılıklar: kurulum (setup) kodunun içinde referansı olan tüm reaktif değerlerin listesi. Reaktif değerler prop’ları, state’i ve bileşeninizin gövdesi içinde bildirilen tüm değişkenleri ve fonksiyonları içerir. Linter’ınız React için yapılandırılmış ise, her reaktif değerin bağımlılık olarak doğru bir şekilde belirtildiğini doğrulayacaktır. Bağımlılık listesi sabit sayıda öğeye sahip olmalı ve [dep1, dep2, dep3] şeklinde satır içinde yazılmalıdır. React, Object.is karşılaştırmasını kullanarak her bağımlılığı önceki değeri ile karşılaştırır. Eğer bağımlılık listesini boş bırakırsanız, Effect’iniz her yeniden render’dan sonra tekrar çalışacaktır. Bağımlılık dizisi iletmenin, boş dizi iletmenin ve hiç bağımlılık olmaması arasındaki farkı inceleyin.

Dönüş Değeri

useEffect, undefined döndürür.

Uyarılar

  • useEffect bir Hook’tur, dolayısıyla bu Hook’u yalnızca bileşeninizin en üst seviyesinde veya kendi Hook’larınızda çağırabilirsiniz. Döngüler veya koşullu ifadeler içinde çağıramazsınız. Eğer çağırmak istiyorsanız, yeni bir bileşen oluşturun ve state’i onun içine taşıyın.

  • Eğer harici sistemle senkronize etmeye çalışmıyorsanız, büyük ihtimalle Effect’e ihtiyacınız yoktur.

  • Strict Modu kullanırken, React ilk gerçek kurulumdan önce sadece geliştirme sırasında olmak üzere ekstra bir kurulum+temizleme döngüsü çalıştırır. Bu, temizleme mantığınızın kurulum mantığınızı “yansıtmasını” ve kurulumun yaptığı her şeyi durdurmasını ya da geri almasını sağlayan bir stres testidir. Eğer bu bir sorun yaratıyorsa, temizleme fonksiyonunu uygulayın.

  • Eğer bağımlılıklarınızdan bazıları nesneler veya bileşeniniz içinde tanımlanmış fonksiyonlar ise, bu bağımlılıkların Effect’in gerekenden daha sık yeniden çalışmasına neden olma riski vardır. Bu durumu düzeltmek için, gereksiz nesne ve fonksiyon bağımlılıklarını silin. Ayrıca state güncellemelerinizi ve reaktif olmayan mantığı Effect dışına taşıyabilirsiniz.

  • Eğer Effect’inizin çalışmasına bir etkileşim (tıklama gibi) neden oluyorsa, React genellikle, Effect’inizi çalıştırmadan önce tarayıcının güncellenen ekranı çizmesine izin verecektir. Eğer Effect’iniz görsel (örneğin ipucu gösterme) bir şey yapıyorsa ve gecikme gözle görülebilir gibiyse (örneğin titriyorsa), useEffect’i useLayoutEffect ile değiştirin.

  • Effect’inizin çalışmasına bir etkileşim (tıklama gibi) neden oluyor olsa bile, tarayıcı Effect’iniz içindeki state güncellemelerini işlemeden önce ekranı yeniden çizebilir. Genellikle, istediğiniz şey budur. Ancak, tarayıcının ekranı yeniden çizmesini engellemek zorundaysanız, useEffect’i useLayoutEffect ile değiştirmelisiniz.

  • Effect’ler sadece kullanıcı (client) tarafında çalışır. Sunucu render etme sırasında çalışmazlar.


Kullanım

Harici bir sisteme bağlanma

Bazı bileşenlerin sayfada görüntülenebilmesi için ağa, bazı tarayıcı API’larına ya da üçüncü parti kütüphanelere bağlı kalması gerekir. Bu sistemler React tarafından kontrol edilmezler, bu yüzden harici olarak adlandırılırlar.

Bileşeninizi harici bir sisteme bağlamak için, bileşeninizin en üst düzeyinde useEffect’i çağırın:

import { useEffect } from 'react';
import { createConnection } from './chat.js';

function ChatRoom({ roomId }) {
const [serverUrl, setServerUrl] = useState('https://localhost:1234');

useEffect(() => {
const connection = createConnection(serverUrl, roomId);
connection.connect();
return () => {
connection.disconnect();
};
}, [serverUrl, roomId]);
// ...
}

useEffect’e iki argüman iletmeniz gerekmektedir:

  1. Bu sisteme bağlanan kurulum (setup) kodu içeren bir kurulum fonksiyonu.
    • Bu sistemle olan bağlantıyı kesen temizleme (cleanup) kodu içeren bir temizleme fonksiyonu döndürmeli.
  2. Bileşeninizden bu fonksiyonların içinde kullanılan her bir değeri içeren bağımlılıklar listesi.

React, kurulum ve temizleme fonksiyonlarınızı gerektiğinde birden çok kez olabilecek şekilde çağırır:

  1. Kurulum kodunuz bileşeniniz sayfaya eklendiğinde çalışır (DOM’a eklendiğinde).
  2. Bileşeninizin bağımlılıklarının değiştiği her yeniden render etmeden sonra:
    • İlk olarak, temizleme kodunuz eski prop’lar ve state ile çalışır.
    • Daha sonra, kurulum kodunuz yeni prop’lar ve state ile çalışır.
  3. temizleme kodunuz son kez bileşeniniz sayfadan kaldırıldığında çalışır (DOM’dan kaldırıldığında).

Yukarıdaki örneği biraz açıklayalım.

Yukarıdaki ChatRoom bileşeni sayfaya eklendiğinde, başlangıç serverUrl ve roomId ile sohbet odasına bağlanacaktır. Eğer serverUrl veya roomId’den biri yeniden render yüzünden değişirse (diyelim ki kullanıcı başka bir sohbet odasını seçerse), Effect’iniz önceki odayla bağlantısını kesecek ve bir sonraki odaya bağlanacaktır. ChatRoom bileşeniniz sayfadan kaldırıldığında, Effect’iniz son bir defa bağlantıyı kesecektir.

Geliştirme sırasında hataları bulmanıza yardımcı olmak için React, kurulum ve temizleme kodunu kurulum’dan önce son kez çalıştırır. Bu, Effect mantığınızın doğru uygulandığını doğrulayan bir stres testidir. Bu, gözle görünür sorunlara neden oluyorsa, temizleme fonksiyonunuzda bazı mantık hataları vardır. Temizleme fonksiyonu, kurulum fonksiyonunun yaptığı her şeyi durdurmalı ya da geri almalıdır. Temel kural, kullanıcı bir kez çağrılan kurulum (son üründe olduğu gibi) ile kurulumtemizlemekurulum sekansı (geliştirme sırasında olduğu gibi) arasındaki farkı ayırt etmemelidir. Sık kullanılan çözümlere göz gezdirin.

Her Effect’i bağımsız bir süreç olarak yazmayı ve her seferinde tek kurulum/temizleme döngüsü düşünmeyi deneyin. Bileşeninizin DOM’a ekleniyor/çıkarılıyor ya da güncelleniyor olması fark etmemelidir. Temizleme mantığınız kurulum mantığını doğru bir şekilde “yansıttığında”, Effect’iniz kurulum ve temizlemeyi gerektiği sıklıkta çalıştıracaktır.

Not

Effect, bileşeninizi harici bir sistemle senkronize tutmanızı (sohbet servisi gibi) sağlar. Burada harici sistem, React tarafından kontrol edilmeyen herhangi bir kod parçası demektir. Örneğin:

Eğer herhangi bir harici sisteme bağlanmıyorsanız, büyük ihtimalle Effect’e ihtiyacınız yoktur.

Harici bir sisteme bağlanma örnekleri

Örnek 1 / 5:
Sohbet sunucusuna bağlanma

Bu örnekte, ChatRoom bileşeni chat.js’de bildirilen harici sisteme bağlı kalmak için Effect’i kullanmaktadır. “Sohbeti aç” butonuna tıklayarak ChatRoom bileşenini render edin. Bu sandbox geliştirme modunda çalışmaktadır, bu yüzden fazladan bir bağlan ve bağlantıyı kes döngüsü burada açıklandığı gibi vardır. roomId ve serverUrl’yi aşağı doğru açılan menüyü (dropdown) ve input’u kullanarak değiştirin ve Effect’in nasıl tekrardan sohbete bağlandığını görün. “Sohbeti kapat” butonuna tıklayarak Effect’in son kez bağlantıyı kesmesini görün.

import { useState, useEffect } from 'react';
import { createConnection } from './chat.js';

function ChatRoom({ roomId }) {
  const [serverUrl, setServerUrl] = useState('https://localhost:1234');

  useEffect(() => {
    const connection = createConnection(serverUrl, roomId);
    connection.connect();
    return () => {
      connection.disconnect();
    };
  }, [roomId, serverUrl]);

  return (
    <>
      <label>
        Sunucu URL'i:{' '}
        <input
          value={serverUrl}
          onChange={e => setServerUrl(e.target.value)}
        />
      </label>
      <h1>{roomId} odasına hoş geldiniz!</h1>
    </>
  );
}

export default function App() {
  const [roomId, setRoomId] = useState('general');
  const [show, setShow] = useState(false);
  return (
    <>
      <label>
        Sohbet odasını seçin:{' '}
        <select
          value={roomId}
          onChange={e => setRoomId(e.target.value)}
        >
          <option value="genel">Genel</option>
          <option value="seyahat">Seyahat</option>
          <option value="muzik">Müzik</option>
        </select>
      </label>
      <button onClick={() => setShow(!show)}>
        {show ? 'Sohbeti kapat' : 'Sohbeti aç'}
      </button>
      {show && <hr />}
      {show && <ChatRoom roomId={roomId} />}
    </>
  );
}


Effect’leri özel Hook’larla sarma

Effect’ler “kaçış kapaklarıdır”: Effect’leri “React’in dışına çıkmanız” gerektiğinde ve kullanım durumunuz için daha iyi yerleşik bir çözüm olmadığunda kullanırsınız. Kendinizi Effect’leri sık sık manuel olarak yazma durumunda buluyorsanız, bu genellikle bileşenlerinizin dayandığı yaygın davranışlar için özel Hook’lar yazmanız gerektiği anlamına gelir.

Örneğin, bu useChatRoom özel Hook’u, Effect’inizin mantığını daha bildirimsel (declarative) bir API’ın arkasına “gizler”:

function useChatRoom({ serverUrl, roomId }) {
useEffect(() => {
const options = {
serverUrl: serverUrl,
roomId: roomId
};
const connection = createConnection(options);
connection.connect();
return () => connection.disconnect();
}, [roomId, serverUrl]);
}

Yazdığınız bu Hook’u herhangi başka bir bileşenden de şöyle kullanabilirsiniz:

function ChatRoom({ roomId }) {
const [serverUrl, setServerUrl] = useState('https://localhost:1234');

useChatRoom({
roomId: roomId,
serverUrl: serverUrl
});
// ...

Ayrıca React ekosisteminde her amaca uygun çok sayıda mükemmel özel Hook’lar mevcuttur.

Effect’leri özel Hook’larla sarma konusunda daha fazla bilgi edinin.

Effect'leri özel Hook'larla sarmaya örnekler

Örnek 1 / 3:
Özel useChatRoom Hook’u

Bu örnek daha önceki örneklerden biriyle benzerdir ancak mantık özel bir Hook’a yazılmıştır.

import { useState } from 'react';
import { useChatRoom } from './useChatRoom.js';

function ChatRoom({ roomId }) {
  const [serverUrl, setServerUrl] = useState('https://localhost:1234');

  useChatRoom({
    roomId: roomId,
    serverUrl: serverUrl
  });

  return (
    <>
      <label>
        Sunucu URL'i:{' '}
        <input
          value={serverUrl}
          onChange={e => setServerUrl(e.target.value)}
        />
      </label>
      <h1>{roomId} odasına hoş geldiniz!</h1>
    </>
  );
}

export default function App() {
  const [roomId, setRoomId] = useState('general');
  const [show, setShow] = useState(false);
  return (
    <>
      <label>
        Sohbet odasını seçin:{' '}
        <select
          value={roomId}
          onChange={e => setRoomId(e.target.value)}
        >
          <option value="genel">Genel</option>
          <option value="seyahat">Seyahat</option>
          <option value="muzik">Müzik</option>
        </select>
      </label>
      <button onClick={() => setShow(!show)}>
        {show ? 'Sohbeti kapat' : 'Sohbeti aç'}
      </button>
      {show && <hr />}
      {show && <ChatRoom roomId={roomId} />}
    </>
  );
}


React olmayan widget’ı kontrol etme

Bazen, harici bir sistemi bileşeninizin bazı prop’larına ya da state’ine göre senkronize etmek istersiniz.

Örneğin, React olmadan yazılmış bir üçünü parti harita widget’ınız veya bir video oynatıcı bileşeniniz varsa, o bileşenin state’ini React bileşeninizin şu anki state’iyle eşleştiren metodları çağırmak için Effect’i kullanabilirsiniz. Bu Effect, map-widget.js içinde tanımlanan bir MapWidget sınıfı örneği oluşturur. Map bileşeninin zoomLevel prop’unu değiştirdiğizde, Effect sınıf örneğini senkronize tutmak için setZoom() fonksiyonunu çağırır:

import { useRef, useEffect } from 'react';
import { MapWidget } from './map-widget.js';

export default function Map({ zoomLevel }) {
  const containerRef = useRef(null);
  const mapRef = useRef(null);

  useEffect(() => {
    if (mapRef.current === null) {
      mapRef.current = new MapWidget(containerRef.current);
    }

    const map = mapRef.current;
    map.setZoom(zoomLevel);
  }, [zoomLevel]);

  return (
    <div
      style={{ width: 200, height: 200 }}
      ref={containerRef}
    />
  );
}

Bu örnekte, MapWidget sınıfı yalnızca kendisine iletilen DOM node’unu yönettiği için bir temizleme fonksiyonu gerekli değildir. Map React bileşeni ağaçtan kaldırıldıktan sonra, hem DOM node’u hem de MapWidget sınıf örneği, tarayıcı JavaScript motoru tarafından otomatik olarak temizlenecektir.


Effect’ler ile veri getirme (fetching)

Bileşeninize veri getirmek için Effect’i kullanabilirsiniz. Eğer bir çatı kullanıyorsanız, çatının veri getirme mekanizmasını kullanmanın Effect’i manuel olarak yazmaktan çok daha verimli olacağını unutmayın.

Eğer manuel olarak Effect ile veri getirmek istiyorsanız, kodunuz şöyle görünebilir:

import { useState, useEffect } from 'react';
import { fetchBio } from './api.js';

export default function Page() {
const [person, setPerson] = useState('Alice');
const [bio, setBio] = useState(null);

useEffect(() => {
let ignore = false;
setBio(null);
fetchBio(person).then(result => {
if (!ignore) {
setBio(result);
}
});
return () => {
ignore = true;
};
}, [person]);

// ...

Başlangıçta false olan ve temizleme sırasında true olan ignore değişkenine dikkat edin. Bu, kodunuzun “yarış koşullarından” zarar görmemesini sağlar: ağdan gelen yanıtlar sizin onları gönderdiğiniz sıradan farklı olabilir.

import { useState, useEffect } from 'react';
import { fetchBio } from './api.js';

export default function Page() {
  const [person, setPerson] = useState('Alice');
  const [bio, setBio] = useState(null);
  useEffect(() => {
    let ignore = false;
    setBio(null);
    fetchBio(person).then(result => {
      if (!ignore) {
        setBio(result);
      }
    });
    return () => {
      ignore = true;
    }
  }, [person]);

  return (
    <>
      <select value={person} onChange={e => {
        setPerson(e.target.value);
      }}>
        <option value="Alice">Alice</option>
        <option value="Bob">Bob</option>
        <option value="Taylor">Taylor</option>
      </select>
      <hr />
      <p><i>{bio ?? 'Yükleniyor...'}</i></p>
    </>
  );
}

async / await sözdizimini kullanarak da yeniden yazabilirsiniz, ancak yine de bir temizleme fonksiyonu sağlamanız gerekmektedir:

import { useState, useEffect } from 'react';
import { fetchBio } from './api.js';

export default function Page() {
  const [person, setPerson] = useState('Alice');
  const [bio, setBio] = useState(null);
  useEffect(() => {
    async function startFetching() {
      setBio(null);
      const result = await fetchBio(person);
      if (!ignore) {
        setBio(result);
      }
    }

    let ignore = false;
    startFetching();
    return () => {
      ignore = true;
    }
  }, [person]);

  return (
    <>
      <select value={person} onChange={e => {
        setPerson(e.target.value);
      }}>
        <option value="Alice">Alice</option>
        <option value="Bob">Bob</option>
        <option value="Taylor">Taylor</option>
      </select>
      <hr />
      <p><i>{bio ?? 'Yükleniyor...'}</i></p>
    </>
  );
}

Direkt olarak Effect ile veri getirmek tekrarlı hale gelir ve önbelleğe alma ve sunucudan render etme gibi optimizasyonların eklenmesini zorlaştırır. Kendiniz veya topluluk tarafından sağlanan özel bir Hook kullanmak daha kolaydır.

Derinlemesine İnceleme

Effect’ler ile veri getirmeye iyi alternatifler nelerdir?

Effect’ler içinde fetch çağrıları yapmak, özellikle tamamen kullanıcı taraflı uygulamalarda veri getirmenin popüler bir yoludur. Ancak bu, çok manuel bir yaklaşımdır ve önemli dezavantajları vardır:

  • Effect’ler sunucuda çalışmazlar. Bu, sunucu tarafından render edilen ilk HTML’in veri içermeyen bir yükleme state’ini içereceği anlamına gelir. Kullanıcı bilgisayarının tüm bu JavaScript’i indirmesi ve uygulamanızın şimdi verileri yüklemesi gerektiğini keşfetmesi için render etmesi gerekecektir. Bu çok verimli bir yol değildir.
  • Doğrudan Effect ile veri getirmek, “ağ şelaleleri (waterfalls) oluşturmayı kolaylaştırır.” Üst bileşeni render edersiniz, o bileşen veri getirir, alt bileşenleri render eder, daha sonra o bileşenler kendi verilerini getirmeye başlarlar. Eğer internet bağlantınız hızlı değilse, verileri paralel olarak getirmeye göre önemli derecede yavaştır.
  • Doğrudan Effect ile veri getirme, genellikle verileri önceden yüklememeniz veya önbelleğe almamanız anlamına gelir. Örneğin, bileşen DOM’dan kaldırılır ve sonra tekrar DOM’a eklenirse, bileşen aynı veriyi tekrar getirmek zorundadır.
  • Ergonomik değildir. Yarış koşulları gibi hatalardan zarar görmeyecek şekilde fetch çağrıları yaparken oldukça fazla genel hatlarıyla kod yazmanız gerekmektedir.

Bu dezavantajlar listesi React’e özel değildir. Bu, herhangi bir kütüphane ile DOM’a eklenme sırasında yapılan veri getirme için geçerlidir. Yönlendirme (routing) de olduğu gibi, veri getirmenin iyi yapılması önemsiz değildir. Bu nedenle aşağıdaki yaklaşımları önermekteyiz:

  • Eğer bir çatı kullanırsanız, çatının yerleşik veri getirme mekanizmasını kullanın. Modern React çatıları verimli veri getirme mekanizmalarını entegre etmişlerdir ve yukarıdaki tehlikelerden uzak dururlar.
  • Aksi halde, kullanıcı taraflı bir önbellek çözümü kullanmayı ya da kendiniz oluşturmayı düşünün. Popüler açık kaynak çözümleri arasında React Query, useSWR ve React Router 6.4+ vardır. Kendi çözümlerinizi de oluşturabilirsiniz. Kendi çözümünüzü uygularsanız, arka planda Effect’leri kullanır ancak aynı zamanda istekleri tekilleştirmek, yanıtları önbelleğe almak ve ağ şelalelerinden kaçınmak (verileri önceden yükleyerek veya veri gereksinimlerini rotalara kaldırarak) gibi mantıkları da ekleyebilirsiniz.

Eğer bu yaklaşımlardan hiçbiri size uymuyorsa, Effect’ler içinde veri getirmeye devam edebilirsiniz.


Reaktif bağımlılıkları belirleme

Effect’inizin bağımlılıklarını “seçemeyeceğinize” dikkat edin. Effect’iniz tarafından kullanılan her reaktif değer bağımlılık olarak bildirilmelidir. Effect’inizin bağımlılık listesi çevreleyen kod tarafından belirlenir:

function ChatRoom({ roomId }) { // Bu reaktif bir değerdir
const [serverUrl, setServerUrl] = useState('https://localhost:1234'); // Bu da reaktif bir değerdir

useEffect(() => {
const connection = createConnection(serverUrl, roomId); // Effect bu reaktif değerleri okur
connection.connect();
return () => connection.disconnect();
}, [serverUrl, roomId]); // ✅ Bu yüzden Effect'inizin bağımlılık listesinde belirtmeniz gerekmektedir
// ...
}

serverUrl veya roomId’den herhangi biri değişirse, Effect’iniz yeni değerleri kullanarak sohbete yeniden bağlanacaktır.

Reaktif değerler, prop’ları ve doğrudan bileşeniniz içinde bildirilen tüm değişkenleri ve fonksiyonları içerir. roomId ve serverUrl reaktif değerler olduğundan dolayı, bu değerleri bağımlılıktan kaldıramazsınız. Eğer kaldırmaya kalkarsanız ve linter’ınız React için ayarlanmışsa, linter bunu düzeltmeniz gereken bir hata olarak işaretleyecektir:

function ChatRoom({ roomId }) {
const [serverUrl, setServerUrl] = useState('https://localhost:1234');

useEffect(() => {
const connection = createConnection(serverUrl, roomId);
connection.connect();
return () => connection.disconnect();
}, []); // 🔴 React Hook'u useEffect'te eksik bağımlılıklar var: 'roomId' and 'serverUrl'
// ...
}

Bağımlılığı kaldırmak için, linter’a bunun bir bağımlıklık olmasına gerek olmadığını “kanıtlamanız” gerekmektedir. Örneğin, reaktif olmadığını ve yeniden render’lar ile değişmeyeceğini kanıtlamak için serverUrl’i bileşeninizin dışına taşıyabilirsiniz:

const serverUrl = 'https://localhost:1234'; // Artık reaktif bir değişken değil

function ChatRoom({ roomId }) {
useEffect(() => {
const connection = createConnection(serverUrl, roomId);
connection.connect();
return () => connection.disconnect();
}, [roomId]); // ✅ Tüm bağımlılıklar bildirilmiş
// ...
}

Artık serverUrl reaktif bir değer olmadığına göre (ve yeniden render’lar ile değişmeyeceğine göre), bağımlılık olmasına gerek yoktur. Eğer Effect kodunuz herhangi bir reaktif değer kullanmıyorsa, bağımlılık listesi boş ([]) olmalıdır:

const serverUrl = 'https://localhost:1234'; // Artık reaktif bir değer değil
const roomId = 'muzik'; // Artık reaktif bir değer değil

function ChatRoom() {
useEffect(() => {
const connection = createConnection(serverUrl, roomId);
connection.connect();
return () => connection.disconnect();
}, []); // ✅ Bütün bağımlılıklar bildirilmiş
// ...
}

Boş bağımlılık listesi olan bir Effect herhangi bir bileşeninizin prop’ları ya da state’i değiştiğinde yeniden çalıştırılmaz.

Tuzak

Eğer var olan bir kod tabanınız varsa, linter’ı şu şekilde yok sayan bazı Effect’leriniz olabilir:

useEffect(() => {
// ...
// 🔴 Linter'ı bu şekilde yok saymaktan kaçının
// eslint-ignore-next-line react-hooks/exhaustive-deps
}, []);

Bağımlılıklar kod ile eşleşmediğinde, hata meydana gelme riski yüksektir. Linter’ı bu şekilde yok sayarak React’e, Effect’inizin bağımlı olduğu değerler konusunda “yalan” söylemiş olursunuz. Bunun yerine gereksiz olduklarını kanıtlayın.

Reaktif bağımlılıkları iletme örnekleri

Örnek 1 / 3:
Bağımlılık dizisi iletme

Eğer bağımlılıkları belirtirseniz, Effect’iniz ilk render’dan ve değişen bağlımlılıklarla yeniden render’lardan sonra çalışacaktır.

useEffect(() => {
// ...
}, [a, b]); // a veya b farklıysa yeniden çalışır

Aşağıdaki örnekte, serverUrl ve roomId reaktif değerlerdir. Bu yüzden her ikisi de bağımlılık olarak belirtilmelidir. Sonuç olarak, aşağı doğru açılan menüden farklı bir oda seçmek ya da sunucu URL’ini değiştirmek sohbete yeniden bağlanılmasına neden olur. Ancak, message Effect’te kullanılmadığından (ve bu yüzden bağımlılık da değil), mesajı düzenlemek sohbete yeniden bağlanmaya neden olmaz.

import { useState, useEffect } from 'react';
import { createConnection } from './chat.js';

function ChatRoom({ roomId }) {
  const [serverUrl, setServerUrl] = useState('https://localhost:1234');
  const [message, setMessage] = useState('');

  useEffect(() => {
    const connection = createConnection(serverUrl, roomId);
    connection.connect();
    return () => {
      connection.disconnect();
    };
  }, [serverUrl, roomId]);

  return (
    <>
      <label>
        Sunucu URL'i:{' '}
        <input
          value={serverUrl}
          onChange={e => setServerUrl(e.target.value)}
        />
      </label>
      <h1>{roomId} odasına hoş geldiniz!</h1>
      <label>
        Mesajınız:{' '}
        <input value={message} onChange={e => setMessage(e.target.value)} />
      </label>
    </>
  );
}

export default function App() {
  const [show, setShow] = useState(false);
  const [roomId, setRoomId] = useState('genel');
  return (
    <>
      <label>
        Sohbet odasını seçin:{' '}
        <select
          value={roomId}
          onChange={e => setRoomId(e.target.value)}
        >
          <option value="genel">Genel</option>
          <option value="seyahat">Seyahat</option>
          <option value="muzik">Müzik</option>
        </select>
        <button onClick={() => setShow(!show)}>
          {show ? 'Sohbeti kapat' : 'Sohbeti aç'}
        </button>
      </label>
      {show && <hr />}
      {show && <ChatRoom roomId={roomId}/>}
    </>
  );
}


Effect’ten önceki state’e göre state’i güncelleme

Effect’ten önceki state’e göre state’i güncellemek istediğinizde, bir sorunla karşılaşabilirsiniz:

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

useEffect(() => {
const intervalId = setInterval(() => {
setCount(count + 1); // Sayacı saniyede bir artırmak istiyorsunuz...
}, 1000)
return () => clearInterval(intervalId);
}, [count]); // 🚩 ... ancak `count`'u bağımlılık olarak belirtmek interval'i sıfırlayacaktır.
// ...
}

count reaktif bir değer olduğundan, bağımlılık listesinde belirtilmek zorundadır. Ancak bu durum, Effect’in her count değiştiğinde temizleme kurulum yapmasına neden olur. Bu ideal bir durum değildir.

Bunu düzeltmek için, c => c + 1 state güncelleyecisini setCount’a iletin:

import { useState, useEffect } from 'react';

export default function Counter() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    const intervalId = setInterval(() => {
      setCount(c => c + 1); // ✅ State güncelleyicisi iletin
    }, 1000);
    return () => clearInterval(intervalId);
  }, []); // ✅ Artık count bir bağımlılık değildir

  return <h1>{count}</h1>;
}

Artık count + 1 yerine c => c + 1 ilettiğimiz için, Effect’inizin count’a bağımlı olmasına gerek yoktur. Bu çözümün sonucu olarak, Effect’iniz count her değiştiğinde temizleme ve kurulum yapmasına gerek yoktur.


Gereksiz nesne bağımlılıklarını kaldırma

Eğer Effect’iniz render esnasında oluşturulan bir nesneye veya fonksiyona bağımlıysa, Effect çok sık çalışabilir. Örneğin bu Effect, options nesnesi her render için farklı olduğundan her render’dan sonra yeniden sohbete bağlanır:

const serverUrl = 'https://localhost:1234';

function ChatRoom({ roomId }) {
const [message, setMessage] = useState('');

const options = { // 🚩 Bu nesne her yeniden render'dan sonra tekrar oluşturulur
serverUrl: serverUrl,
roomId: roomId
};

useEffect(() => {
const connection = createConnection(options); // Effect içinde kullanılır
connection.connect();
return () => connection.disconnect();
}, [options]); // 🚩 Bunun neticesinde, bu bağımlılıklar yeniden render'da her zaman farklıdır
// ...

Render esnasında oluşturulan bir nesneyi bağımlılık olarak kullanmaktan kaçının. Bunun yerine nesneyi Effect içinde oluşturun:

import { useState, useEffect } from 'react';
import { createConnection } from './chat.js';

const serverUrl = 'https://localhost:1234';

function ChatRoom({ roomId }) {
  const [message, setMessage] = useState('');

  useEffect(() => {
    const options = {
      serverUrl: serverUrl,
      roomId: roomId
    };
    const connection = createConnection(options);
    connection.connect();
    return () => connection.disconnect();
  }, [roomId]);

  return (
    <>
      <h1>{roomId} odasına hoş geldiniz!</h1>
      <input value={message} onChange={e => setMessage(e.target.value)} />
    </>
  );
}

export default function App() {
  const [roomId, setRoomId] = useState('genel');
  return (
    <>
      <label>
        Sohbet odasını seçin:{' '}
        <select
          value={roomId}
          onChange={e => setRoomId(e.target.value)}
        >
          <option value="genel">Genel</option>
          <option value="seyahat">Seyahat</option>
          <option value="muzik">Müzik</option>
        </select>
      </label>
      <hr />
      <ChatRoom roomId={roomId} />
    </>
  );
}

Şimdi options nesnesini Effect içinde oluşturduğumuzdan, Effect sadece roomId string’ine bağımlıdır.

Bu çözümle birlikte, input’a yazmak sohbete tekrar bağlanmayacaktır. Her render’da yeniden oluşturulan nesne aksine, roomId gibi bir string siz onu başka bir değere eşitlemediğiniz sürece değişmez. Bağımlılıkları kaldırmak hakkında daha fazlasını okuyun.


Gereksiz fonksiyon bağımlılıklarını kaldırma

Eğer Effect’iniz render esnasında oluşturulan bir nesneye veya fonksiyona bağımlıysa, Effect çok sık çalışabilir. Örneğin bu Effect, createOptions fonksiyonu her render’da farklı olduğundan her render’dan sonra yeniden sohbete bağlanır:

function ChatRoom({ roomId }) {
const [message, setMessage] = useState('');

function createOptions() { // 🚩 Bu fonksiyon her yeniden render'dan sonra sıfırdan tekrar oluşturulur
return {
serverUrl: serverUrl,
roomId: roomId
};
}

useEffect(() => {
const options = createOptions(); // Effect içinde kullanılır
const connection = createConnection();
connection.connect();
return () => connection.disconnect();
}, [createOptions]); // 🚩 Bunun neticesinde, bu bağımlılıklar yeniden render'da her zaman farklıdır
// ...

Her yeniden render’da sıfırdan bir fonksiyon oluşturmak kendi başına bir sorun değildir. Bunu optimize etmenize gerek yoktur. Ancak fonksiyonu Effect’inizin bağımlılığı olarak kullanırsanız, Effect’inizin her yeniden render’dan sonra yeniden çalışmasına neden olacaktır.

Render esnasında oluşturulan bir fonksiyonu bağımlılık olarak kullanmaktan kaçının. Bunun yerine Effect içinde bildirin:

import { useState, useEffect } from 'react';
import { createConnection } from './chat.js';

const serverUrl = 'https://localhost:1234';

function ChatRoom({ roomId }) {
  const [message, setMessage] = useState('');

  useEffect(() => {
    function createOptions() {
      return {
        serverUrl: serverUrl,
        roomId: roomId
      };
    }

    const options = createOptions();
    const connection = createConnection(options);
    connection.connect();
    return () => connection.disconnect();
  }, [roomId]);

  return (
    <>
      <h1>{roomId} odasına hoş geldiniz!</h1>
      <input value={message} onChange={e => setMessage(e.target.value)} />
    </>
  );
}

export default function App() {
  const [roomId, setRoomId] = useState('genel');
  return (
    <>
      <label>
        Sohbet odasını seçin:{' '}
        <select
          value={roomId}
          onChange={e => setRoomId(e.target.value)}
        >
          <option value="genel">Genel</option>
          <option value="seyahat">Seyahat</option>
          <option value="muzik">Müzik</option>
        </select>
      </label>
      <hr />
      <ChatRoom roomId={roomId} />
    </>
  );
}

Şimdi createOptionsfonksiyonunu Effect içinde bildirdiğimizden, Effect sadece roomId string’ine bağlıdır. Böylelikle input’u değiştirmek sohbete tekrar bağlanmayacaktır. Her render’da yeniden oluşturulan fonksiyon yerine, roomId gibi bir string siz onu başka değere eşitlemediğiniz sürece değişmez. Bağımlılıkları kaldırmak hakkında daha fazlasını okuyun.


Effect’te nihai prop’ları ve state’i okuma

Yapım Halinde

Bu bölümde, React’in stabil sürümünde henüz yayınlanmamış deneysel bir API anlatılmaktadır.

Varsayılan olarak, Effect’ten reaktif bir değer okuduğunuz zaman bu değeri bağımlılık olarak eklemeniz gerekmektedir. Bu, Effect’inizin o değer her değiştiğinde “tepki” vermesini sağlar. Çoğu bağımlılık için istediğiniz davranış budur.

Ancak bazen, nihai prop’ları ve state’i Effect bunlara “tepki” vermeden okumak isteyeceksiniz. Örneğin, her sayfa ziyareti için alışveriş sepetindeki ürünlerin sayısını kaydetmek istediğinizi hayal edin:

function Page({ url, shoppingCart }) {
useEffect(() => {
logVisit(url, shoppingCart.length);
}, [url, shoppingCart]); // ✅ Tüm bağımlılıklar bildirilmiş
// ...
}

Ya url her değiştiğinde yeni bir sayfa ziyareti kaydetmek istiyorsanız ancak sadece shoppingCart değiştiğinde kaydetmek istemiyorsanız? Reaktivite kurallarını çiğnemeden shoppingCart’ı bağımlılıklardan çıkartamazsınız. Ancak, Effect içinden çağırılsa bile bir kod parçasının yapılan değişikliklere “tepki” vermesini istemediğinizi ifade edebilirsiniz. useEffectEvent Hook’u ile Effect Olayı bildirin ve shoppingCart’ı okuyan kodu onun içine taşıyın:

function Page({ url, shoppingCart }) {
const onVisit = useEffectEvent(visitedUrl => {
logVisit(visitedUrl, shoppingCart.length)
});

useEffect(() => {
onVisit(url);
}, [url]); // ✅ Tüm bağımlılıklar bildirilmiş
// ...
}

Effect Olayları reaktif değillerdir ve Effect’inizin bağımlılıklarından kaldırılmalıdırlar. Bu, reaktif olmayan kodunuzu (prop’ların ve state’in nihai değerini okuyabildiğiniz) Effect’in içine koymanızı sağlar. shoppingCart’ı onVisit içinde okuyarak, shoppingCart’ın Effect’inizi yeniden çalıştırmamasını sağlarsınız.

Effect Olaylarının reaktif ve reaktif olmayan kodu ayırmanızı nasıl sağladığı hakkında daha fazla bilgi edinin.


Sunucu ve kullanıcıda farklı içerikler gösterme

Uygulamanız sunucu render etme kullanıyorsa (ya direkt olarak ya da çatı kullanarak), bileşeniniz iki farklı ortamda render edilecektir. Sunucuda, başlangıç HTML’ini oluşturmak için render edecektir. Kullanıcıda, React olay yönetecilerini HTML’e eklemek için render etme kodunu yeniden çalıştıracaktır. Bu nedenle, hidrasyon işleminin çalışması için, ilk render çıktınızın kullanıcı ve sunucuda aynı olması gerekir.

Bazı nadir durumlarda, kullanıcıda farklı içerik göstermek isteyebilirsiniz. Örneğin, uygulamanız localStorage’dan bazı veriler okuyorsa, bu işlemi sunucudan yapamaz. Bunu şu şekilde uygulayabilirsiniz:

function MyComponent() {
const [didMount, setDidMount] = useState(false);

useEffect(() => {
setDidMount(true);
}, []);

if (didMount) {
// ... yalnızca kullanıcı JSX'i döndür ...
} else {
// ... ilk JSX'i döndür ...
}
}

Uygulama yüklenirken, kullanıcı ilk render çıktısını görecektir. Daha sonra, uygulama yüklendiğinde ve hidrasyon olduğunda, Effect’iniz çalışarak didMount state’ini true yapacak ve yeniden render tetikleyecektir. Bu kullanıcı-taraflı (client-side) render çıktısıyla değişecektir. Effect’ler sunucuda çalışmazlar, bu yüzden ilk server render’ı sırasında didMount state’i false’a eşittir.

Bu modeli idareli kullanın. Yavaş bir bağlantıya sahip kullanıcılar ilk içeriği oldukça uzun bir süre (potansiyel olarak saniyelerce) göreceğinden, bileşeninizin görünüşünde büyük değişiklikler yapmak istemezsiniz. Çoğu durumda, CSS ile koşullu olarak farklı şeyler göstererek buna ihtiyaç duymazsınız.


Sorun giderme

Bileşen DOM’a eklendiğinde Effect’im iki kere çalışıyor

Geliştirmede Strict modu açıkken, React kurulum ve temizleme işlemini asıl kurulumdan önce bir kere fazladan çalıştırır.

Bu, Effect mantığınızın doğru uygunlanıdığını doğrulayan bir stres testidir. Eğer bu, gözle görülebilir sorunlara neden oluyorsa, temizleme fonksiyonunuzda mantık hatası vardır. Temizleme fonksiyonu, kurulum fonksiyonunun yaptığı her şeyi durdurmalı veya geri almalıdır. Temel kural, kullanıcı bir kez çağrılan kurulum (son üründe olduğu gibi) ile kurulumtemizlemekurulum sekansı (geliştirme sırasında olduğu gibi) arasındaki farkı ayırt etmemelidir.

Bunun nasıl hataları bulmanıza yardımcı olacağı ve mantığınızı nasıl düzelteceğiniz hakkında daha fazla bilgi edinin.


Effect’im her yeniden render’dan sonra çalışıyor

İlk olarak bağımlılık dizisini belirtmeyi unutup unutmadığınızı kontrol edin:

useEffect(() => {
// ...
}); // 🚩 Bağımlılık dizisi yok: her yeniden render'dan sonra yeniden çalışır!

Bağımlılık dizisini belirttiyseniz ancak Effect’iniz hala döngüde yeniden çalışyorsa, bunun nedeni bağımlılıklarınızdan birinin her yeniden render’da farklı olmasıdır.

Bağımlılıkları konsola manuel olarak yazdırarak bu hatayı ayıklayabilirsiniz:

useEffect(() => {
// ..
}, [serverUrl, roomId]);

console.log([serverUrl, roomId]);

Daha sonra konsoldaki farklı yeniden render’ların dizilerine sağ tıklayıp her ikisi için de “Global değişken olarak sakla“‘yı seçebilirsiniz. İlkinin temp1 olarak ve ikincinin temp2 olarak kaydedildiğini varsayarsak, her iki dizideki her bir bağımlılığın aynı olup olmadığını kontrol etmek için tarayıcı konsolunu kullanabilirsiniz:

Object.is(temp1[0], temp2[0]); // İlk bağımlılık diziler arasında aynı mı?
Object.is(temp1[1], temp2[1]); // İkinci bağımlılık diziler arasında aynı mı?
Object.is(temp1[2], temp2[2]); // ... ve diğer bağımlılıklar için ...

Her yeniden render’da farklı olan bağımlılığı bulduğunzda, genellikle şu yollardan biriyle düzeltebilirsiniz:

Son çare olarak (bu yöntemler yardımcı olmadıysa), useMemo veya useCallback (fonksiyonlar için) kullanın.


Effect’im sonsuz bir döngüde sürekli çalışıyor

Effect’iniz sonsuz bir döngüde çalışıyorsa, şu iki şey doğru olmak zorundadır:

  • Effect’iniz bir state’i güncelliyor.
  • O state, Effect’in bağımlılıklarının değişmesine neden olan bir yeniden render tetikliyor.

Sorunu çözmeye başlamadan önce, Effect’inizin harici bir sisteme (DOM, ağ veya üçüncü parti widget gibi) bağlanıp bağlanmadığını kendinize sorun. Effect’iniz neden state’i değiştiriyor? Harici sistem ile senkronizasyon mu yapıyor? Yoksa uygulamanızın veri akışını Effect ile mi yönetmeye çalışıyorsunuz?

Harici bir sistem yoksa, Effect’i tamamen kaldırmanın mantığınızı basitleştirip basitleştirmeyeceğine bakın.

Eğer gerçekten harici bir sistem ile senkronizasyon yapıyorsanız, Effect’inizin neden ve hangi koşullarda state’i güncellemesi gerektiğini düşünün. Bileşeninizin görsel çıktısını etkileyen bir değişiklik mi oldu? Render sırasında kullanılmayan bazı verileri takip etmeniz gerekiyorsa, ref (yeniden render tetiklemez) daha uygun olabilir. Effect’inizin state’i gereğinden fazla güncellemediğini (ve yeniden render’lar tetiklemediğini) doğrulayın.

Son olarak, Effect’iniz state’i doğru zamanda güncelliyorsa ancak yine de bir döngü söz konusuysa, bunun nedeni, state güncellemesinin Effect’in bağımlılıklarından birinin değişmesine neden olmasıdır. Bağımlılık değişikliklerinden kaynaklı hataların nasıl ayıklanacağını okuyun.


Temizleme mantığım bileşenim DOM’dan kaldırılmasa bile çalışıyor

Temizleme fonksiyonu sadece DOM’dan kaldırılma sırasında değil, değişen bağımlılıklarla her yeniden render’dan önce de çalışır. Ek olarak, geliştirme aşamasında, React kurulum+temizleme fonksiyonlarını bileşen DOM’a eklendikten hemen sonra bir kez daha çalıştırır.

Bir temizleme kodunuz var ancak kurulum kodunuz yoksa, bu genellike kötü kokan bir koddur (code smell):

useEffect(() => {
// 🔴 Kaçının: Kurulum mantığı olmadan temizleme mantığı var
return () => {
doSomething();
};
}, []);

Temizleme mantığınız kurulum mantığıyla “simetrik” olmalı ve kurulumun yaptığı her şeyi durdurmalı veya geri almalıdır:

useEffect(() => {
const connection = createConnection(serverUrl, roomId);
connection.connect();
return () => {
connection.disconnect();
};
}, [serverUrl, roomId]);

Effect yaşam döngüsünün bileşenin yaşam döngüsünden ne kadar farklı olduğunu öğrenin.


Effect’im görsel bir şey yapıyor ve çalışmadan önce bir titreme görüyorum

Effect’iniz tarayıcının ekranı çizmesini engelliyorsa, useEffect’i useLayoutEffect ile değiştirin. Bunu yapmaya Effect’lerin büyük çoğunluğu için ihtiyaç duyulmaması gerektiğini unutmayın. Buna yalnızca Effect’inizi tarayıcı ekranı çizmeden önce çalıştırmanız çok önemliyse ihtiyacanız olacak: örneğin, bir tooltip’ini kullanıcı görmeden önce ölçmek ve konumlandırmak için.