9 min read

React function component lifecycle: useState, useEffect

Bu yazıda React functional component'lerin yaşam döngüsünü inceliyoruz. Function component'lerde class component'lerden farklı olarak constructor ve onMount vb. metotlardan yararlanamıyoruz. Bunun yerine useState() ve useEffect() hook'larından yardım alıyoruz.


State tutmak için useState() hook kullanımı

Function component'lerde constructor bulunmadığından state oluşturmak ve ilk değerini vermek için useState() hook kullanılır.

Örneğin "count" adında bir sayı tutulacak ve ilk değeri sıfır olacaksa, şu şekilde tanımlanır.

const App = () => {
  const [count, setCount] = React.useState(0);
  
  return (
    <div>
      <h1>{count}</h1> 
    </div>
  );
};

ReactDOM.render(<App />, document.querySelector("#app"))

Bu değeri yani state'i kullanıcının değiştirebilmesi için "set" ile başlayan metodu kullanırız. Buradaki tanımda ilk ifade olan count state'in adını, ikinci ifade olan setCount ise state'i değiştirmekte kullanacağımız metodun adını belirtir.

Eğer bu metodu kullanmadan state'i değiştirirseniz React bu farkı anlamaz ve bileşeni yeniden render etmez.
const App = () => {
  const [count, setCount] = React.useState(0); 
  const add = () => setCount(prev => prev + 1);  
  return (
    <div>
      <h1>{count}</h1> 
      <button type="button" onClick={add}>
        +1
      </button>     
    </div>
  );
};

ReactDOM.render(<App />, document.querySelector("#app"))

Burada kullanıcının +1 butonunu tıklamasıyla setCount komutu çalışır ve state'i günceller. Yeni state'de count=1 olur.  

Bileşenin state'i değiştiğinden hemen render edilir.

Component'in güncellenmesi

Function component'in state'i değiştiğinde yeniden render edilecektir. Bunu konsola veya ekrana yazdırarak da takip edebiliriz.

const App = () => {
  const [count, setCount] = React.useState(0);
  console.log("App rendered at: " + new Date().toLocaleString());
 
  const add = () => setCount(prev => prev + 1);
 
  return (
    <div>
      <h1>{count}</h1> 
      <p>Rendered at: {new Date().toLocaleString()}</p>
      <button type="button" onClick={add}>+1</button>      
    </div>
  );
};

ReactDOM.render(<App />, document.querySelector("#app"))

Bu örnekte App bileşeni state'i değiştiği zaman yeniden render edilecek. Biz de bunu konsola yazılan mesajla ve ekrana yazılan tarih-saat bilgisiyle görebileceğiz.

Siz de bu örnekte +1 butonuna basarak her defasında konsola yeni bir mesaj yazıldığını, ve ekrandaki "Rendered at:.." kısmında da en son render edildiği zamanın gösterildiğini teyit edebilirsiniz.

Üst bileşen render edildiğinde otomatik olarak bütün alt bileşenler de render edilir

Bir component render edildiğinde otomatik olarak hiyerarşide daha altta bulunan component'ler de tekrar render edilir. Bunu bir örnekle görebiliriz.

const Hello = () => (
<div style={{backgroundColor:"beige"}}>
<p>Hello</p>
<p>Hello Rendered at: {new Date().toLocaleString()}</p>
</div>
);
const Hello1 = ({name}) => (
<div style={{backgroundColor:"lime"}}>
<p>Hello {name}</p>
<p>Hello1 Rendered at: {new Date().toLocaleString()}</p>
</div>
);
const App = () => {
  const [count, setCount] = React.useState(0);
  console.log("App rendered at: " + new Date().toLocaleString());
 
  const add = () => setCount(prev => prev + 1);
 
  return (
    <div>
      <h1>{count}</h1> 
      <p>Rendered at: {new Date().toLocaleString()}</p>
      <button type="button" onClick={add}>+1</button>   
      <Hello />
      <Hello1 name="Joe" />
    </div>
  );
};

ReactDOM.render(<App />, document.querySelector("#app"))

Bu örnekte App bileşeni +1 ile state değiştiğinde yeniden render ediliyor. Bu sebeple onun altındaki diğer iki bileşen de yeniden render ediliyor. Tarihlerin aynı oluşundan hepsinin yeniden render edildiğini anlıyoruz.

Alt bileşenin state'i güncellendiğinde üsttekiler yeniden render edilmez

Üstteki koda ek olarak kendi state'i olan bir function component tanımlayalım.

const Hello2 = () => {
const [count,setCount] = React.useState(0);
return (
<div style={{backgroundColor:"orange"}}>
<p>Hello2 {count}</p>
<p>Hello2 Rendered at: {new Date().toLocaleString()}</p>
 <button type="button" onClick={()=>setCount(x=>x+1)}>+1</button>
</div>
);
}

Bu component içerisindeki +1 butonu tıklandığında yeniden render edilir. Fakat onun dışındaki bileşenler aynen kalır.

const Hello = () => (
<div style={{backgroundColor:"beige"}}>
<p>Hello</p>
<p>Hello Rendered at: {new Date().toLocaleString()}</p>
</div>
);
const Hello1 = ({name}) => (
<div style={{backgroundColor:"lime"}}>
<p>Hello {name}</p>
<p>Hello1 Rendered at: {new Date().toLocaleString()}</p>
</div>
);
const Hello2 = () => {
const [count,setCount] = React.useState(0);
return (
<div style={{backgroundColor:"orange"}}>
<p>Hello2 {count}</p>
<p>Hello2 Rendered at: {new Date().toLocaleString()}</p>
 <button type="button" onClick={()=>setCount(x=>x+1)}>+1</button>
</div>
);
}
const App = () => {
  const [count, setCount] = React.useState(0);
  console.log("App rendered at: " + new Date().toLocaleString());
 
  const add = () => setCount(prev => prev + 1);
 
  return (
    <div>
      <h1>{count}</h1> 
      <p>Rendered at: {new Date().toLocaleString()}</p>
      <button type="button" onClick={add}>+1</button>   
      <Hello />
      <Hello1 name="Joe" />
      <Hello2 />
    </div>
  );
};

ReactDOM.render(<App />, document.querySelector("#app"))

Bu örneği çalıştırdığımızda App bileşeni ve altındaki diğer bileşenlerin render tarihlerinin yeni eklediğimiz bileşenden etkilenmediğini görebiliyoruz.

İşte bu şekilde bir sayfanın devamlı yeniden render edilmesi engellenmiş oluyor.

Alttaki bileşenlerin bazıları üsttekinin yenilenmesinden etkilenmeyebilir

Normalde bir bileşen üstteki yeniden render edildiğinde kendisi de render edilir. Fakat eğer alttaki bileşenin props değeri değişmemişse bu gereksiz bir render olacaktır. Bunu engellemek için React.memo kullanabiliriz.

Aşağıdaki iki component görünüşte benzese de ikincisinde React.memo() hook kullanılıyor. Bunun anlamı, üstteki bileşen yenilendiğinde eğer "count" değeri değişmemişse ikinci component render edilmeyecek, bunun yerine önceki değeri aynen ekrana yazılacak.

const NoMemoComp = ({ count }) => {
  console.log('No memo comp render edildi'); 
  return <h1>{count}</h1>;
};

const MemoizedComp = React.memo(({ count }) => {
  console.log('Memo comp render edildi'); 
  return <h1>{count}</h1>;
});

React.memo esasında bileşenin çıktısının değişmediği durumlarda önceki çıktıyı ezberleyerek yeniden render etmek yerine aynı değeri yazar. Bu şekilde bizi render işleminin maliyetinden kurtarır.

Özellikle zaman alan tablo, vb karmaşık bileşenlerde gereksiz bekleme yapılmaması için kullanılır.

Aşağıdaki örnekte input alanına yazı yazıldığında "name" state değiştiğinden App bileşeni yeniden render edilir. Bu sebeple alt bileşenler de yeniden render edilir. Hepsinin render zamanlarının güncellendiğini görebilirsiniz.

const Hello = () => (
<div style={{backgroundColor:"beige"}}>
<p>Hello</p>
<p>Hello Rendered at: {new Date().toLocaleString()}</p>
</div>
);
const Hello1 = ({name}) => (
<div style={{backgroundColor:"lime"}}>
<p>Hello {name}</p>
<p>Hello1 Rendered at: {new Date().toLocaleString()}</p>
</div>
);
const Hello2 = () => {
const [count,setCount] = React.useState(0);
const [num,setNum] = React.useState(0);
React.useEffect(()=>{
	console.log("Hello2 count is: " + count);
},[count]);
return (
<div style={{backgroundColor:"orange"}}>
<p>Hello2 count: {count}</p>
<p>Hello2 num: {num}</p>
<p>Hello2 Rendered at: {new Date().toLocaleString()}</p>
 <button type="button" onClick={()=>setCount(x=>x+1)}>count +1</button>
  <button type="button" onClick={()=>setNum(x=>x+1)}>num +1</button>
</div>
);
}

const TimerComp = ()=>{
const [secs,setSecs] = React.useState(0);
React.useEffect(()=>{
	const interval = setInterval(() => {    
    setSecs(prev=>prev+1);
  }, 1000);
  return () => clearInterval(interval);
},[]);
return(<p>Time since mount: {secs} seconds.</p>)
}

const NoMemoComp = ({ count }) => {
  console.log('No memo comp render edildi'); 
  return <h1>{count}</h1>;
};

const MemoizedComp = React.memo(({ count }) => {
  console.log('Memo comp render edildi'); 
  return <h1>{count}</h1>;
});

const App = () => {
  const [count, setCount] = React.useState(0);
  const [name,setName] = React.useState("");
  //console.log("App rendered at: " + new Date().toLocaleString());
 
  const add = () => setCount(prev => prev + 1);
  
  React.useEffect(()=>{
  	console.log("App rendered at:" + new Date().toLocaleString())
  }); 
 
  return (
    <div>
      <h1>{count}</h1> 
      <p>Rendered at: {new Date().toLocaleString()}</p>
      <button type="button" onClick={add}>+1</button>   
      <Hello />
      <Hello1 name="Joe" />
      <Hello2 />
      <TimerComp />
      <input type="text" onChange={(e)=>setName(e.target.value)}></input>
      <NoMemoComp count={count} />
      <MemoizedComp count={count} />
    </div>
  );
};

ReactDOM.render(<App />, document.querySelector("#app"))

Güncellenmeyen sadece memo() hook ile sarmalanan bileşendir. Onun çıktısı değişmeyeceğinden yeniden render edilmemiştir. Bu bileşen sadece "count" değeri +1 butonu ile artılırdığında render edilir. Siz de örnek üzerinde deneyerek görebilirsiniz.


useEffect() hook ile her render'dan sonra belirli işlemler otomatik olarak yapılabilir

Bir bileşen render edildiğinde hemen ardından bazı işlemlerin yeniden yapılmasını isteyebiliriz. Örneğin bir animasyon efekti, API'dan veri istenmesi, vb. Bunları useEffect() hook ile tanımlayabiliyoruz.

Örneğin render tarihini konsola yazmak için useEffect() kullanabiliriz.

React.useEffect(()=>{
  	console.log("App rendered at:" + new Date().toLocaleString())
  });

useEffect() ile sadece belirli bir state değiştiğinde otomatik işlemler yapılabilir

useEffect() hook ikincil bir parametre alabilir. Bu parametrenin değeri değiştiğinde (default olarak shallow comparison yapılır) useEffect() hook çalışacaktır. Diğer state'ler değişse de bu hook çalışmaz.

Örneğin iki farklı sayıyı değiştiren iki ayrı buton yapalım. Bunlardan sadece birinin tıklanması halinde useEffect() hook çalıştıralım.

const Hello2 = () => {
const [count,setCount] = React.useState(0);
const [num,setNum] = React.useState(0);
React.useEffect(()=>{
	console.log("Hello2 count is: " + count);
},[count]);
return (
<div style={{backgroundColor:"orange"}}>
<p>Hello2 count: {count}</p>
<p>Hello2 num: {num}</p>
<p>Hello2 Rendered at: {new Date().toLocaleString()}</p>
 <button type="button" onClick={()=>setCount(x=>x+1)}>count +1</button>
  <button type="button" onClick={()=>setNum(x=>x+1)}>num +1</button>
</div>
);
}

Bu metot her state değişiminde değil, sadece "count" değeri değiştiğinde çalışır.

Düzenli aralıklarla tekrarlanan işlemler yapılabilir

Bir bileşen render edildiğinden yok edilene kadar geçen sürede düzenli aralıklarla (dakikada bir gibi) bir işlemi tekrarlayabilir.

Örnek olarak dakikada bir ekrana yazdığı sayıyı bir artıran bir bileşen yazalım. Bunun için setInterval() metodundan yararlanacağız.

const TimerComp = ()=>{
const [secs,setSecs] = React.useState(0);
React.useEffect(()=>{
	const interval = setInterval(() => {    
    setSecs(prev=>prev+1);
  }, 1000);
  return () => clearInterval(interval);
},[]);
return(<p>Time since mount: {secs} seconds.</p>)
}

Burada useEffect() hook'ta başlattığımız sayacın daha sonra bileşen yokedildiğinde kaldırılabilmesi için return ederek clearInterval() metodu çağırıyoruz. Aksi halde bu sayaç çalışmaya devam eder.

Bu örnekle aslında altında bulunduğu bileşenin ilk kez render edilmesinin üzerinden kaç saniye geçtiğini tutan bir sayaç yapmış olduk.

Aşağıdaki kodu çalıştırdığınızda bileşenler yeniden render edilse de bu sayaç işlemeye devam edecektir.

const Hello = () => (
<div style={{backgroundColor:"beige"}}>
<p>Hello</p>
<p>Hello Rendered at: {new Date().toLocaleString()}</p>
</div>
);
const Hello1 = ({name}) => (
<div style={{backgroundColor:"lime"}}>
<p>Hello {name}</p>
<p>Hello1 Rendered at: {new Date().toLocaleString()}</p>
</div>
);
const Hello2 = () => {
const [count,setCount] = React.useState(0);
const [num,setNum] = React.useState(0);
React.useEffect(()=>{
	console.log("Hello2 count is: " + count);
},[count]);
return (
<div style={{backgroundColor:"orange"}}>
<p>Hello2 count: {count}</p>
<p>Hello2 num: {num}</p>
<p>Hello2 Rendered at: {new Date().toLocaleString()}</p>
 <button type="button" onClick={()=>setCount(x=>x+1)}>count +1</button>
  <button type="button" onClick={()=>setNum(x=>x+1)}>num +1</button>
</div>
);
}

const TimerComp = ()=>{
const [secs,setSecs] = React.useState(0);
React.useEffect(()=>{
	const interval = setInterval(() => {    
    setSecs(prev=>prev+1);
  }, 1000);
  return () => clearInterval(interval);
},[]);
return(<p>Time since mount: {secs} seconds.</p>)
}
const App = () => {
  const [count, setCount] = React.useState(0);
  //console.log("App rendered at: " + new Date().toLocaleString());
 
  const add = () => setCount(prev => prev + 1);
  
  React.useEffect(()=>{
  	console.log("App rendered at:" + new Date().toLocaleString())
  }); 
 
  return (
    <div>
      <h1>{count}</h1> 
      <p>Rendered at: {new Date().toLocaleString()}</p>
      <button type="button" onClick={add}>+1</button>   
      <Hello />
      <Hello1 name="Joe" />
      <Hello2 />
      <TimerComp />
    </div>
  );
};

ReactDOM.render(<App />, document.querySelector("#app"))