React Hooks Nedir?

Betül Toyoğlu
6 min readSep 7, 2024

--

Merhaba, bu yazımda React Hooks kavramını daha iyi anlamaya çalışırken aldığım bazı notlardan yola çıkarak sizleri de bu macerama dahil edeceğim, keyifli okumalar!😊

Öncelikle React 16.8'deki bir eklenti olan Hook’lar, bir sınıf yazmadan state ve diğer React özelliklerini kullanmaya olanak sağlar. Peki nedir bu “state”? Javascript’te bir değer saklayacağımız zaman var, let, const tanımlamalarını kullandığımız gibi React bileşenlerinde de değer saklamak için state’ten yararlanırız. Sınıfların aksine, state bir nesne olmak zorunda değildir. Sayı ya da string tutabilirler. Normalde bu state’leri kullanabilmek, Constructor içinde erişebilmek için “this” nesnesini kullanırız. Ancak Hook’lar sayesinde bu erişim işlemi daha da kolaylaşıyor.

Hook’ları kullanabilmek için en üstte import etmemiz gerekir. Kullanmak istediğiniz her bir yeni Hook süslü parantezler arasına virgül ile eklenir.

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

Hook çeşitlerine başlamadan önce uymamız gereken kurallara bir göz atalım:

▶️ Hook’lar sadece React fonksiyon bileşenleri içinde çağrılabilirler. Sınıflar içinde kullanılamazlar ki zaten sınıfların Hook görevi gören yapıları bulunur. Hook’lar React’ı sınıflar olmadan kullanmamıza yararlar.

▶️ Hook’lar her zaman bileşenin en üst seviyesinde çağrılabilirler.

▶️ Hook’lar koşullu olarak çağrılamazlar. Yani, bir Hook bir bileşenin render döngüsünde her zaman aynı sırada çağrılmalıdır. Eğer Hook’u bir koşulun içine koyarsanız, bazı render’larda o Hook çağrılmayabilir ve bu da React’in Hook’ları izleme mekanizmasını bozar. Bu yüzden, Hook’lar döngüler ya da koşullar içerisinde yer almamalıdır. Doğru kullanım şu şekilde olmalıdır:

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

if (someCondition) {
// Koşula bağlı kod buraya gelebilir
}

// Diğer Hooks buraya gelir
}

Son olarak Hook çeşitlerine başlamadan önce, Hook’lar tekrar tekrar kullanılabilir fonksiyonlar olduğu için kendi Hook’larınızı “use” ile başlayarak yazabileceğinizi de eklemek istiyorum.

📌 useState

useState, fonksiyonel bir bileşene yerel bir state eklemek için çağırlır.

useState bir başlangıç (default) state’i alır — örnekte bu “red” oluyor — ve isimlerini bizim verdiğimiz iki değer döndürür : Mevcut (current) state ve o state’i güncelleyen fonksiyon.

function FavoriteColor() {
const [color, setColor] = useState("red");

Şimdi ise sıra state’i istediğimiz yerde kullanmakta.

<p>My favorite color is {color} !</p>

color state’imizi güncellemek için tek yapmamız gereken setColor değişkenini kullanmak.

<button type="button" onClick={() => setColor("blue")}> Blue </button>

Ayrıca birden fazla state kullanmak istediğimiz durumlarda farklı state değişkenlerine farklı adlar vermemize izin verdiği için şu şekilde bir tanımlama gerçekleştirebiliriz:

function ManyStates() {
const [age, setAge] = useState(42);
const [fruit, setFruit] = useState('banana');
const [cars, setCars] = useState([{ text: 'Red car' }]);

Ya da, sadece bir state kullanıp obje ekleyebiliriz:

function Car() {
const [car, setCar] = useState({
brand: "Ford",
model: "Mustang",
year: "1964",
color: "red"
});

📌 useEffect

useEffect Hook’u, bileşenlerde side effect (yan etki) etkileşimleri uygulamamızı sağlar. Peki side effect ne anlama gelir? React bileşenleri verilen girdilere göre bir çıktı üretirler. Ancak veri almak, DOM’u güncellemek, zamanlayıcılar kullanmak gibi bileşenlerin dış dünyayla etkileşime geçtiği durumlarda yan etkiler oluşabilir. Dış dünya dediğimiz kavram kısaca React kapsamının dışında kalan şeyler olur yani mesela buna React framework’une dahil olmayan şeyler diyebiliriz. Bu tür durumları doğru bir şekilde yönetebilmek içinse useEffect kullanılır. useEffect fonksiyonel bir bileşene yan etkileri kullanabilme yetkisi ekler.

useEffect iki parametre alır : useEffect(<fonksiyon>, <bağımlılıklar>). Burada bağımlılık parametresi opsiyoneldir.

Mesela setTimeout() zamanlayıcısını useEffect ile kullanmaya çalışalım:

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

useEffect(() => {
setTimeout(() => {
setCount((count) => count + 1);
}, 1000);
});

Burada başlangıçtaki amacımız sadece 1 kez saymaktı ama bağımlılığı kullanmadığımız için durum böyle olmadı. Çünkü useEffect hem ilk oluşturulmadan sonra hem de her güncellemeden sonra çalıştırılır. Bunu kontrol etmek için array isteyen bağımlılığı devreye sokmamız gerekir.

useEffect(() => {
//Sadece ilk oluştuğunda çalışır
}, []);

Üstteki örnekteki gibi boş bir array kullanabileceğimiz gibi props ya da state değeri de kullanabiliriz.

useEffect(() => {
//Sadece ilk oluştuğunda çalışır
}, [prop, state]);

📌 useContext

“React Context”, React uygulamalarında verilerin paylaşımını sağlamak için kullanılan bir API’dir. Özellikle, bir bileşenin alt bileşenlerine veri göndermek için “prop drilling” sorununu aşmak için idealdir.

React’te bileşenler, props’ları kullanarak bir üst bileşenden bilgi alabilir. Prop’lar üst bileşenden bir alt bilşene veri geçirmenin bir yolu olarak hizmet eder. Prop drilling ise verinin hemen hemen her seviyeye gönderildiği durumdur ve bu büyük bir karmaşaya yol açabilir. useContext ile kullanım sağlayabildiğimiz React Context bu durumun önüne geçmeyi amaçlar.

Öncelikle “createContext”i import etmemiz ve tanımlamamız gerekir.

import { useState, createContext } from "react";

const UserContext = createContext()

createContext, bir Provider ve Consumer bileşeni ile gelir. Provider, Context’i sağlamak için kullanılır. Alt bileşenler bu Provider’ın içinde yer aldıklarında context değerine erişebilirler.

function App() {
const [user, setUser] = useState("Jesse Hall");

return (
<UserContext.Provider value={user}>
<h1>{`Hello ${user}!`}</h1>
<ChildComponent user={user} />
</UserContext.Provider>
);
}

Consumer ise Context’i tüketmek için kullanılır. Alt bileşenler, context’teki verilere erişmek için bu bileşeni kullanır.

import { useState, createContext, useContext } from "react";

function ChildComponent() {
const user = useContext(UserContext);

return (
<>
<h2>{`Hello ${user} again!`}</h2>
</>
);
}

📌 useRef

useRef, render’lar arasında değerlerin/referansların kalıcı olarak kalmasını ve kaybolmamasını sağlar. Güncellendiğinde render’a neden olmayacak değiştirilebilen (mutable) değerleri depolamak ve bir DOM elemanına direkt ulaşım sağlamak için kullanılabilir.

Butona her tıklanışında countRef değerini güncelleyip bileşenin render edilmemesini şu şekilde sağlayabiliriz:

function Timer() {
const countRef = useRef(0);

const handleIncrement = () => {
countRef.current += 1;
console.log(countRef.current);
};

return (
<div>
<button onClick={handleIncrement}>Increment</button>
</div>
);
}

export default Timer;

DOM elemanlarına erişmek istersek de yine useRef kullanabiliriz. Bir öğeye doğrudan DOM üzerinden erişmek için ona “ref” niteliği ekleyebiliriz:

function App() {
const inputElement = useRef();

const focusInput = () => {
inputElement.current.focus();
};

return (
<>
<input type="text" ref={inputElement} />
<button onClick={focusInput}>Focus Input</button>
</>
);
}

📌 useReducer

useReducer, useState’in bir alternatifidir. (state, action) => newState şeklinde bir reducer fonskiyonu ve başlangıç değeri parametre olarak alır. Mevcut (current) state’i bir dispatch metodu ile döndürür.

useReducer(<reducer>, <initialState>)

Kısa bir hatırlatma geçmek istersek, Redux’ta “Action” uygulamada gerçekleşen her türlü etkileşimi veya olayı temsil ederken, “Reducer” eylemlere dayalı olarak mevcut durumu güncellemek için kullanılır. Dispatch metodu ise eylemleri tetikler. Eylemler bu yöntem aracılığıyla taşınarak durumu güncellemek için reducer’lara iletilir :)

Peki bu nasıl mı kullanılır?

const initialState = {count: 0};

const reducer = (state, action) => {
switch (action.type) {
case 'increment':
return {count: state.count + 1};
case 'decrement':
return {count: state.count - 1};
default:
throw new Error();
}
}

function Counter() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<>
Count: {state.count}
<button onClick={() => dispatch({type: 'decrement'})}>-</button>
<button onClick={() => dispatch({type: 'increment'})}>+</button>
</>
);
}

📌 useCallback

useCallback, ezberlenmiş yani hafızadaki yeri korunmuş bir callback fonksiyonu döndürür. Daha anlaşılır bir hale getirmeye çalışırsak, bu Hook belirli bir fonsksiyonun yeniden oluşturulmasını önleyerek performansı optimize etmek için kullanılır. Bu, özellikle performans açısından kritik durumlarda, gereksiz render’ları ve yeniden hesaplamaları azaltmak için faydalıdır.

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

// useCallback ile fonksiyonu oluştur
const increment = useCallback(() => {
setCount((prevCount) => prevCount + 1);
}, []); // Bağımlılık dizisi boş, bu yüzden sadece bir kez oluşturulacak

return (
<div>
<p>Count: {count}</p>
<button onClick={increment}>Increment</button>
</div>
);
}

export default Counter;

📌 useMemo

useMemo, ezberlenmiş bir değer döndürür. useMemo ve useCallback birbirine çok benzerdir. Aralarındaki en büyük fark, useMemo bir değer döndürürken useCallback bir fonskiyon döndürür.

useMemo da performansı arttırmak için kullanılır ve belirli bir değerin yalnızca bağımlılık dizisinde değişiklikler olduğunda yeniden hesaplamasını sağlar.

Aşağıdaki örnekte fonksiyon sadece count değeri değiştiğinde çalışır, todo eklenmesi durumu etkilemez:

const App = () => {
const [count, setCount] = useState(0);
const [todos, setTodos] = useState([]);
const calculation = useMemo(() => expensiveCalculation(count), [count]);

const increment = () => {
setCount((c) => c + 1);
};
const addTodo = () => {
setTodos((t) => [...t, "New Todo"]);
};

return (
<div>
<div>
<h2>My Todos</h2>
{todos.map((todo, index) => {
return <p key={index}>{todo}</p>;
})}
<button onClick={addTodo}>Add Todo</button>
</div>
<hr />
<div>
Count: {count}
<button onClick={increment}>+</button>
<h2>Expensive Calculation</h2>
{calculation}
</div>
</div>
);
};

const expensiveCalculation = (num) => {
console.log("Calculating...");
for (let i = 0; i < 1000000000; i++) {
num += 1;
}
return num;
};

Buraya kadar okuduğunuz için teşekkür ederim!

--

--