Back to blog

(React) Hook Nedir, Temel Hooklar ve Kendi Hookunuzu Yapma

Published 01/15/2022

(React) Hook Nedir, Temel Hooklar ve Kendi Hookunuzu Yapma

Hooklar React'ın 16.8.0 sürümü ile bizlere tanıtılan birkaç fonksiyondan ibarettir. Amacı ise en kaba tabirle class componentleri ile yapılan her şeyi fonksiyonel componentler ile yaptırabilmeyi sağlamak.

Hookların amacı class componentleri tamamen ortadan kaldırmak değildir ancak günümüz dünyasında class componentler zaten pek de ilgi çeken bir yapı değil. Ancak hook olmazsa da fonksiyonel componentlerde state yönetimi gibi birçok React nimeti çok sıkıntı yaşatır. Sonuç olarak elmizde artık hook API'si bulunmakta!

Hooklar genel olarak React özelliklerini fonksiyonel komponentlerde kullanmanızı sağlayan özel fonksiyonlardır. Örneğin useState hooku, React state'ini bir fonksiyonel componente eklemenizi sağlayan bir hooktur.

Örnek Bir Hook Kullanımı

Burada örnek olması adına bir state hooku kullanacağız. İlk olarak eskiden yapmanız gereken componenti ve ardından bu makaleden sonra nasıl yapmanız gerektiğini örneklendiren birkaç kod bloğu göreceğiz.

Klasik class component yapısıyla yapılmış bir kod alalım elimize

import React, { Component } from "react";

class MyComponent extends Component {
	constructor(props) {
		super(props);
		this.state = {
			count: 0;
		}
	}
}

Gördüğünüz gibi componentimiz hem çok uzun hem de göz korkutucu. Basit bir state için oldukça fazla satır yazmamız gerekiyor. Şimdi bir de bu state'i güncellemeye çalışalım:

import React, { Component } from "react";

class MyComponent extends Component {
	constructor(props) {
		super(props);
		this.state = {
			count: 0;
		}
	}

	render() {
		return (
			<div>
				<span>{this.state.count} defa tıklandı.
				<button onClick={
					() => this.setState({ count: this.state.count + 1})
				}>
					Tıkla
				</button>
			</div>
		);
	}
}

Gördüğünüz gibi kod karışmaya başladı bile. Artık hook örneğine geçebiliriz.

import React, { useState } from "react";

const MyComponent = () => {
	const [count, setCount] = useState(0);
}

Gördüğünüz gibi hem hızımızı arttırdık hem de düzenimizi. Ayrıca artık daha rahat bir state yönetimine sahibiz.

Şimdi bir de bu state'i güncellemeye çalışalım:

import React, { useState } from "react";

const MyComponent = () => {
	const [count, setCount] = useState(0);
	
	return (
		<div>
			<span>{count} defa tıklandı.
			<button onClick={
				() => setCount(count + 1)
			}>
				Tıkla
			</button>
		</div>
		);
}

Gördüğünüz üzere kodumuzda inanılmaz bir kısalma var ve artık daha rahat okunabilir halde.

Hookların Temel Kuralları

Hooklar her ne kadar JavaScript fonksiyonları olsa da bu fonksiyonları şu iki temel kurala uyarak kullanmanız gerekmektedir:

  • Hookları sadece kodunuzun en üstünde kullanın. Herhangi bir loop içinde veya başka bir fonksiyon içinde hook kullanmayın. Sadece fonksiyonle componentinizin return kısmından önce kullanın. Böylece componentimiz tekrar tekrar renderlandığında hookunuzun aktifleştiğinden emin olursunuz.
  • Sadece fonksiyonel componentlerde hook kullanın. Başka fonksiyonlar içinde veya class componentleri içerisinde hook kullanamazsınız.

Temel Hooklar

1 - useState

useState hooku klasik class componentlerindeki state özelliğini fonksiyonel componentimize eklememizi sağlayan bir hooktur.

import React, { useState } from "react";

const MyComponent = () => {
	// useState kullanım örneği
	const [count, setCount] = useState(0);
}

Componentimizde bir değişiklik olduğunda tekrar renderlanmasını istiyorsak kesinlikle kullanmamız gereken bir hooktur. Basitçe hooku anlamak istersek hooku çağırırken girdiğimiz argüman state'imizin başlangıç değeri olacaktır. Yukarıdaki örnekte count state'inin varsayılan değeri olarak 0 ayarladık. Yani componentimiz ilk renderlandığında count değeri 0 olacaktır.

Hookumuzu çağırdıktan sonra bize 2 elementten oluşan bir array verecektir.

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

Bu arrayın ilk elementi bizim state'imizi, ikincisi ise bu state'i güncellememizi sağlayacak fonksiyonu barındırır. State'imizi güncellemek istediğimizde bu fonksiyonu çağırmamız lazım.

const [count, setCount] = useState(0);
count // >> 0
setCount(3);
count // >> 3

Eğer const, let veya var kullanarak bir değer oluşturursak bu değerleri ne kadar güncellersek güncelleyelim componentimiz yeniden renderlanmaz ve güncel veriyi göremeyiz.

import React, { useState } from "react";

const MyComponent = () => {
	// doğru kullanım
	const [count, setCount] = useState(0);
	
	// yanlış kullanım
	let count = 0;
}

2 - useEffect

useEffect hookunun 4 temel işlevi vardır. Bu hooku genel olarak klasik class componentlerinde bulunan componentDidMount, componentDidUpdate ve componentWillUnMount metotlarının yerini tutması için kullanırız. 4. işlevi ise spesifik bir state güncellenmesini takip etmektir.

componentDidUpdate

Componentimiz her renderlandığında bir kod çalıştırmamızı sağlayan bir metottur.

import React, { Component } from "react";

class MyComponent extends Component {
	componentDidUpdate() {
		console.log("Componentimiz yeniden renderlandı.");
	}
}

Bu özelliği fonksiyonel componentler ile şu şekilde yapıyoruz:

import React, { useEffect } from "react";

const MyComponent = () => {
	useEffect(() => {
		console.log("Componentimiz yeniden renderlandı.");
	});
}

useEffect hooku gördüğünüz gibi herhangi bir veri returnlamıyor. Yani sadece componentimizin renderlanması sonucu yapmamız gereken değişimleri yönettiğimiz bir hooktur.

componentDidMount

Componentimiz ilk kez renderlandığında sadece 1 kez çalışmasını istediğimiz bir metottur. Örneğin bir websocket bağlantısı yapacaksınız veya bir interval oluşturacaksınız. Bunları component her renderlandığında tekrar tekrar yaparsanız veriler birbirine karışır. Sonuç olarak sadece 1 kere yapmak istediğimiz için bu işlemleri componentDidMount içinde yaparız.

import React, { Component } from "react";

class MyComponent extends Component {
	constructor(props) {
		super(props);
		this.state = {
			interval: undefined,
		};
	}
	
	componentDidMount() {
		const i = setInterval(() => {
			console.log("Bu mesaj her 10 saniyede bir loglanacaktır.");
		}, 1000 * 10);
		this.setState({ interval: i });
		console.log("Componentimiz ilk kez renderlandı ve bu fonksiyon bir daha çağırılmayacaktır.");
	}
}

Bu özelliği fonksiyonel componentler ile şu şekilde yapıyoruz:

import React, { useState, useEffect } from "react";

const MyComponent = () => {
	const [interval, setIntervalState] = useState();
	
	useEffect(() => {
		const i = setInterval(() => {
			console.log("Bu mesaj her 10 saniyede bir loglanacaktır.");
		}, 1000 * 10);
		setIntervalState(i);
		console.log("Componentimiz ilk kez renderlandı ve bu fonksiyon bir daha çağırılmayacaktır.");

	// ⋁⋁⋁ buraya dikkat ediniz
	}, []);
}

Gördüğünüz üzere useEffect hookumda boş bir array belirttim. Burayı şimdilik kafanıza takmayın, birazdan onun amacını tam anlamıyla anlatacağım.

Böylece fonksiyonel componentimizde de componentDidMount metodunu çağırmış olduk.

componentWillUnMount

Componentimiz görüş alanımızdan çıkacağında (yani renderlanmayacağında, dokümanımızdan kaldırılacağında) çağırılan bir metottur. Bu metot sayesinde son temizlikleri yapabilir ve performansı arttırabiliriz. Bu metodu daha iyi anlatabilmek için yukarıdaki componentDidMount örneğini biraz geliştireceğim.

import React, { Component } from "react";

class MyComponent extends Component {
	constructor(props) {
		super(props);
		this.state = {
			interval: undefined,
		};
	}
	
	componentWillUnMount() {
		// Son temizliğimizi yaptık ve artık performans sıkıntısı yaşamayacağız.
		clearInterval(this.state.interval);
		console.log("Component dokümandan kaldırıldı.");
	}
	
	componentDidMount() {
		const i = setInterval(() => {
			console.log("Bu mesaj her 10 saniyede bir loglanacaktır.");
		}, 1000 * 10);
		this.setState({ interval: i });
		console.log("Componentimiz ilk kez renderlandı ve bu fonksiyon bir daha çağırılmayacaktır.");
	}
}

Bu özelliği fonksiyonel componentler ile şu şekilde yapıyoruz:

import React, { useState, useEffect } from "react";

const MyComponent = () => {
	const [interval, setIntervalState] = useState();
	
	useEffect(() => {
		const i = setInterval(() => {
			console.log("Bu mesaj her 10 saniyede bir loglanacaktır.");
		}, 1000 * 10);
		setIntervalState(i);
		console.log("Componentimiz ilk kez renderlandı ve bu fonksiyon bir daha çağırılmayacaktır.");
	
		// burası "componentWillUnMount" metodu
		return () => {
			// Son temizliğimizi yaptık ve artık performans sıkıntısı yaşamayacağız.
			clearInterval(interval);
			console.log("Component dokümandan kaldırıldı.");
		}
	}, []);
}

useEffect hooku içerisinde bir fonksiyon returnlarsak bu fonksiyon, componentimiz dokümandan kaldırılırken çağırılacaktır.

Spesifik state takibi

useEffect hookundaki favorim. Bu özellik sayesinde sadece belirttiğimiz state'te bir değişim olduğu zaman bir işlem yapabiliyoruz.

import React, { useState } from "react";

const MyComponent = () => {
	const [count, setCount] = useState();
	const [name, setName] = useState();
}

Örneğin yukarıda 2 adet state verimiz bulunmakta ancak bir sadece name state'i değiştiği zaman bir işlem yapmak istiyoruz. O zaman işin içine dependency array dediğimiz bir olay giriyor. Yukarıda useEffect'in componentDidMount karşılığını anlatırken boş bir array belirtmiştim. Burası tam da onunla ilgili.

useEffect hookunu çağırırken ilk parametre olarak effect fonksiyonunu belirtiyoruz, bunu geçtiğimiz birkaç örnekte anladık. Ancak useEffect hookuna 2 adet parametre tanımlayabiliriz. İkinci parametre ise dependency array olarak geçiyor. Bu parametre bir array ([]) olmak zorunda. Bu array içine koyduğumuz veriler hookumuzun dependency'si oluyor. Yani bu verilerde bir güncellenme olduğu zaman hookumuz tekrar çalıştırılıyor.

import React, { useState, useEffect } from "react";

const MyComponent = () => {
	const [count, setCount] = useState();
	const [name, setName] = useState();

	useEffect(() => {
		console.log("Bu hook componentimiz her renderlandığında çalışacaktır.");
	});

	useEffect(() => {
		console.log("Bu hook yalnızca 1 kere (ilk renderdan sonra) çalışacaktır.");
	}, []);

	useEffect(() => {
		console.log("Bu hook yalnızca 'name' verisi güncellenince çalışacaktır.");
	}, [name]);
}

Yani dependency array'i boş bırakmak demek bu hooku yalnızca 1 kere (ilk renderdan sonra) çalışmak üzere ayarlamak demek oluyor.

Kendi Hookumuzu Oluşturma

Hooklar bu yazının başında da bahsettiğim gibi birer özel JavaScript fonksiyonlarıdır. Bir fonksiyon içinde başka fonksiyonlar da çağırabilir veya aynı kodu birden çok yerde kullanıyorsak bunu fonksiyon haline getirip işimizi kolaylaştırabiliriz. Yani bu demek oluyor ki fonksiyonlar için geçerli olan her şey hooklar için de geçerlidir. Bir hook içinde başka hooklar da çağırabilir ve aynı hooku birden çok yerde kullanabiliriz.

Şimdi basit bir hook yapmaya çalışalım. Bu yapacağımız hookun amacı belirttiğimiz URL'ye bir request atması ve bu requestin sonucunu vermesi olsun.

İlk olarak temel bir hook yapısı oluşturalım:

import React, { useState } from "react";

export const useFetch = (url) => {
	const [body, setBody] = useState();
	const [error, setError] = useState();

	return [body, error];
}

Gördüğünüz gibi hookumuzun içerisinde de useState hookunu kullandık. Böylece bir veri güncellemesi yaptığımızda componentimiz tekrar renderlanacaktır. Şimdi request özelliğini entegre edelim:

import React, { useState, useEffect } from "react";

export const useFetch = (url) => {
	const [loading, setLoading] = useState(true);
	const [body, setBody] = useState();
	const [error, setError] = useState();

	const fetcher = async () => {
		setLoading(true);
		const res = await fetch(url);
		if (!res.ok) {
			setError(res.statusText);
			setLoading(false);
			return;
		}
		const body = await res.json();
		setBody(body);
		setError();
		setLoading(false);
	}

	useEffect(fetcher, []);

	return [loading, body, error, fetcher];
}

Ve hookumuz tamamlandı! Fetcher fonksiyonu ile bir fetch yapıyoruz ve hata varsa setError ile hata state'ini güncelliyoruz, yoksa setBody ile body state'ini güncelliyoruz. Bu hooku artık şu şekilde kullanabiliriz:

import React, { useState } from "react";
import { useFetcher } from "./path/to/useFetcher";

const MyComponent = () => {
	const [loading, body, error] = useFetcher("https://pokeapi.co/api/v2/pokemon/ditto");

	return loading ? (
		<div>Yükleniyor...</div>
	) : error ? (
		<div>Bir hata oluştu.</div>
	) : (
		<div>Pokemonun adı: {body?.name}</div>
	)
}

Son Sözler

Yazıyı buraya kadar okuduysanız artık hookların ne demek olduğunu, nasıl çalıştıklarını ve kendi hookunuzu nasıl yapabileceğinizi anlamışsınızdır demektir. Zaman ayırdığınız için teşekkürler, başka bir yazıda görüşmek üzere.



© 2020 - 2022 All rights reserved. Made with by barisbored using NextJS and TailwindCSS