Ders İçeriği

Hooks Nedir? Neden Kullanılır?

React Hooks, React 16.8 sürümüyle birlikte tanıtılan ve fonksiyonel bileşenlerde state yönetimi ve yaşam döngüsü işlemlerini mümkün kılan özel fonksiyonlardır. Hooks'tan önce, state yönetimi ve yaşam döngüsü metotları sadece sınıf bileşenlerinde kullanılabiliyordu. Hooks sayesinde, fonksiyonel bileşenler de bu güçlü özelliklere sahip oldu.

Hooks'un temel avantajları:

Kod Yeniden Kullanımı: Hooks, bileşenler arasında stateful mantığı paylaşmayı kolaylaştırır. Özel hooks oluşturarak, karmaşık mantığı farklı bileşenlerde yeniden kullanabilirsiniz.

Daha Basit Kod: Fonksiyonel bileşenler, sınıf bileşenlerinden daha az kod yazımı gerektirir ve daha okunabilirdir. Hooks sayesinde, karmaşık yaşam döngüsü mantığını daha basit bir şekilde yönetebilirsiniz.

Daha İyi Test Edilebilirlik: Fonksiyonel bileşenler ve hooks, birim testleri yazmayı daha kolay hale getirir.

Performans Optimizasyonu: Hooks, React'in optimizasyon özelliklerini daha etkili kullanmanıza olanak tanır.

Temel Hooks (useState, useEffect, useContext)

useState Hook'u

useState hook'u, fonksiyonel bileşenlerde state yönetimi için kullanılır. Daha önceki derslerde temel kullanımını görmüştük, şimdi daha detaylı inceleyelim.

Temel Kullanım

import React, { useState } from 'react';


function Counter() {

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

  

  return (

    <div>

      <p>Sayaç: {count}</p>

      <button onClick={() => setCount(count + 1)}>Artır</button>

    </div>

  );

}

Fonksiyonel State Güncellemesi

State güncellemesi yaparken, önceki state değerine bağlı olarak güncelleme yapacaksanız, fonksiyonel güncelleme kullanmalısınız:

function Counter() {

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

  

  const increment = () => {

    // Yanlış yaklaşım - race condition'a neden olabilir

    // setCount(count + 1);

    

    // Doğru yaklaşım - fonksiyonel güncelleme

    setCount(prevCount => prevCount + 1);

  };

  

  const incrementTwice = () => {

    // Bu şekilde iki kez artırım garantilenir

    setCount(prevCount => prevCount + 1);

    setCount(prevCount => prevCount + 1);

  };

  

  return (

    <div>

      <p>Sayaç: {count}</p>

      <button onClick={increment}>Bir Artır</button>

      <button onClick={incrementTwice}>İki Artır</button>

    </div>

  );

}

Karmaşık State Yönetimi

Karmaşık objeler ve diziler için useState kullanımı:

function UserProfile() {

  const [user, setUser] = useState({

    name: '',

    email: '',

    preferences: {

      theme: 'light',

      notifications: true

    }

  });

  

  const updateName = (newName) => {

    setUser(prevUser => ({

      ...prevUser,

      name: newName

    }));

  };

  

  const updatePreferences = (key, value) => {

    setUser(prevUser => ({

      ...prevUser,

      preferences: {

        ...prevUser.preferences,

        [key]: value

      }

    }));

  };

  

  return (

    <div>

      <input 

        value={user.name}

        onChange={(e) => updateName(e.target.value)}

        placeholder="İsim"

      />

      <label>

        <input 

          type="checkbox"

          checked={user.preferences.notifications}

          onChange={(e) => updatePreferences('notifications', e.target.checked)}

        />

        Bildirimleri etkinleştir

      </label>

      <select 

        value={user.preferences.theme}

        onChange={(e) => updatePreferences('theme', e.target.value)}

      >

        <option value="light">Açık Tema</option>

        <option value="dark">Koyu Tema</option>

      </select>

    </div>

  );

}

Lazy Initial State

State'in başlangıç değeri hesaplaması maliyetli ise, lazy initialization kullanabilirsiniz:

function ExpensiveComponent() {

  // Bu fonksiyon sadece ilk render'da çalışır

  const [data, setData] = useState(() => {

    console.log('Pahalı hesaplama yapılıyor...');

    return Array.from({ length: 1000 }, (_, i) => ({

      id: i,

      value: Math.random()

    }));

  });

  

  return (

    <div>

      <p>Veri sayısı: {data.length}</p>

      <button onClick={() => setData([])}>Temizle</button>

    </div>

  );

}

useEffect Hook'u (Yaşam Döngüsü Metotları)

useEffect hook'u, fonksiyonel bileşenlerde yan etkiler (side effects) gerçekleştirmek için kullanılır. Bu yan etkiler arasında API çağrıları, DOM manipülasyonu, zamanlayıcılar ve temizlik işlemleri yer alır.

Temel useEffect Kullanımı

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


function UserProfile({ userId }) {

  const [user, setUser] = useState(null);

  const [loading, setLoading] = useState(true);

  

  useEffect(() => {

    // Bu kod her render'dan sonra çalışır

    console.log('Bileşen render edildi');

    

    // API çağrısı

    fetchUser(userId)

      .then(userData => {

        setUser(userData);

        setLoading(false);

      })

      .catch(error => {

        console.error('Kullanıcı yüklenemedi:', error);

        setLoading(false);

      });

  });

  

  if (loading) return <div>Yükleniyor...</div>;

  if (!user) return <div>Kullanıcı bulunamadı</div>;

  

  return (

    <div>

      <h2>{user.name}</h2>

      <p>{user.email}</p>

    </div>

  );

}

Dependency Array ile useEffect

useEffect'in ikinci parametresi olan dependency array, effect'in ne zaman çalışacağını kontrol eder:

function UserProfile({ userId }) {

  const [user, setUser] = useState(null);

  const [loading, setLoading] = useState(true);

  

  useEffect(() => {

    setLoading(true);

    fetchUser(userId)

      .then(userData => {

        setUser(userData);

        setLoading(false);

      });

  }, [userId]); // Sadece userId değiştiğinde çalışır

  

  return (

    <div>

      {loading ? (

        <div>Yükleniyor...</div>

      ) : (

        <div>

          <h2>{user.name}</h2>

          <p>{user.email}</p>

        </div>

      )}

    </div>

  );

}

Cleanup Fonksiyonu

useEffect'ten bir fonksiyon döndürerek temizlik işlemleri yapabilirsiniz:

function Timer() {

  const [seconds, setSeconds] = useState(0);

  

  useEffect(() => {

    const interval = setInterval(() => {

      setSeconds(prevSeconds => prevSeconds + 1);

    }, 1000);

    

    // Cleanup fonksiyonu

    return () => {

      clearInterval(interval);

    };

  }, []); // Boş dependency array - sadece mount/unmount'ta çalışır

  

  return <div>Geçen süre: {seconds} saniye</div>;

}

Birden Fazla useEffect

Farklı amaçlar için birden fazla useEffect kullanabilirsiniz:

function UserDashboard({ userId }) {

  const [user, setUser] = useState(null);

  const [posts, setPosts] = useState([]);

  const [onlineStatus, setOnlineStatus] = useState(false);

  

  // Kullanıcı bilgilerini yükle

  useEffect(() => {

    fetchUser(userId).then(setUser);

  }, [userId]);

  

  // Kullanıcının gönderilerini yükle

  useEffect(() => {

    fetchUserPosts(userId).then(setPosts);

  }, [userId]);

  

  // Online durumu takip et

  useEffect(() => {

    const subscription = subscribeToUserStatus(userId, setOnlineStatus);

    

    return () => {

      subscription.unsubscribe();

    };

  }, [userId]);

  

  return (

    <div>

      <h2>{user?.name} {onlineStatus ? '🟢' : '🔴'}</h2>

      <p>Gönderi sayısı: {posts.length}</p>

    </div>

  );

}

useEffect ile Yaşam Döngüsü Simülasyonu

Sınıf bileşenlerindeki yaşam döngüsü metotlarını useEffect ile simüle edebilirsiniz:

function LifecycleDemo({ prop }) {

  const [state, setState] = useState(0);

  

  // componentDidMount

  useEffect(() => {

    console.log('Bileşen mount edildi');

    

    // componentWillUnmount

    return () => {

      console.log('Bileşen unmount edilecek');

    };

  }, []);

  

  // componentDidUpdate (sadece prop değiştiğinde)

  useEffect(() => {

    console.log('Prop değişti:', prop);

  }, [prop]);

  

  // componentDidUpdate (sadece state değiştiğinde)

  useEffect(() => {

    console.log('State değişti:', state);

  }, [state]);

  

  return (

    <div>

      <p>State: {state}</p>

      <button onClick={() => setState(state + 1)}>State'i Artır</button>

    </div>

  );

}

useContext Hook'u (Global State Yönetimi)

useContext hook'u, React Context API'sini fonksiyonel bileşenlerde kullanmak için kullanılır. Context, bileşen ağacında props drilling yapmadan veri paylaşmayı sağlar.

Context Oluşturma ve Kullanma

import React, { createContext, useContext, useState } from 'react';


// Context oluşturma

const ThemeContext = createContext();


// Provider bileşeni

function ThemeProvider({ children }) {

  const [theme, setTheme] = useState('light');

  

  const toggleTheme = () => {

    setTheme(prevTheme => prevTheme === 'light' ? 'dark' : 'light');

  };

  

  return (

    <ThemeContext.Provider value={{ theme, toggleTheme }}>

      {children}

    </ThemeContext.Provider>

  );

}


// useContext hook'u ile context kullanma

function Header() {

  const { theme, toggleTheme } = useContext(ThemeContext);

  

  return (

    <header style={{ 

      backgroundColor: theme === 'light' ? '#fff' : '#333',

      color: theme === 'light' ? '#333' : '#fff'

    }}>

      <h1>Benim Uygulamam</h1>

      <button onClick={toggleTheme}>

        {theme === 'light' ? 'Koyu' : 'Açık'} Tema

      </button>

    </header>

  );

}


function Content() {

  const { theme } = useContext(ThemeContext);

  

  return (

    <main style={{ 

      backgroundColor: theme === 'light' ? '#f5f5f5' : '#222',

      color: theme === 'light' ? '#333' : '#fff',

      padding: '20px'

    }}>

      <p>Bu içerik {theme} temasında görüntüleniyor.</p>

    </main>

  );

}


// Ana uygulama

function App() {

  return (

    <ThemeProvider>

      <div>

        <Header />

        <Content />

      </div>

    </ThemeProvider>

  );

}

Karmaşık State Yönetimi için Context

Daha karmaşık state yönetimi için useReducer ile birlikte kullanılabilir:

import React, { createContext, useContext, useReducer } from 'react';


// Action types

const ACTIONS = {

  ADD_TODO: 'ADD_TODO',

  TOGGLE_TODO: 'TOGGLE_TODO',

  DELETE_TODO: 'DELETE_TODO',

  SET_FILTER: 'SET_FILTER'

};


// Reducer

function todoReducer(state, action) {

  switch (action.type) {

    case ACTIONS.ADD_TODO:

      return {

        ...state,

        todos: [...state.todos, {

          id: Date.now(),

          text: action.payload,

          completed: false

        }]

      };

    case ACTIONS.TOGGLE_TODO:

      return {

        ...state,

        todos: state.todos.map(todo =>

          todo.id === action.payload

            ? { ...todo, completed: !todo.completed }

            : todo

        )

      };

    case ACTIONS.DELETE_TODO:

      return {

        ...state,

        todos: state.todos.filter(todo => todo.id !== action.payload)

      };

    case ACTIONS.SET_FILTER:

      return {

        ...state,

        filter: action.payload

      };

    default:

      return state;

  }

}


// Context

const TodoContext = createContext();


// Provider

function TodoProvider({ children }) {

  const [state, dispatch] = useReducer(todoReducer, {

    todos: [],

    filter: 'all'

  });

  

  return (

    <TodoContext.Provider value={{ state, dispatch }}>

      {children}

    </TodoContext.Provider>

  );

}


// Custom hook

function useTodos() {

  const context = useContext(TodoContext);

  if (!context) {

    throw new Error('useTodos must be used within TodoProvider');

  }

  return context;

}


// Bileşenler

function TodoForm() {

  const [text, setText] = useState('');

  const { dispatch } = useTodos();

  

  const handleSubmit = (e) => {

    e.preventDefault();

    if (text.trim()) {

      dispatch({ type: ACTIONS.ADD_TODO, payload: text });

      setText('');

    }

  };

  

  return (

    <form onSubmit={handleSubmit}>

      <input 

        value={text}

        onChange={(e) => setText(e.target.value)}

        placeholder="Yeni görev ekle"

      />

      <button type="submit">Ekle</button>

    </form>

  );

}


function TodoList() {

  const { state, dispatch } = useTodos();

  

  const filteredTodos = state.todos.filter(todo => {

    if (state.filter === 'completed') return todo.completed;

    if (state.filter === 'active') return !todo.completed;

    return true;

  });

  

  return (

    <div>

      <div>

        <button onClick={() => dispatch({ type: ACTIONS.SET_FILTER, payload: 'all' })}>

          Tümü

        </button>

        <button onClick={() => dispatch({ type: ACTIONS.SET_FILTER, payload: 'active' })}>

          Aktif

        </button>

        <button onClick={() => dispatch({ type: ACTIONS.SET_FILTER, payload: 'completed' })}>

          Tamamlanan

        </button>

      </div>

      <ul>

        {filteredTodos.map(todo => (

          <li key={todo.id}>

            <span 

              style={{ 

                textDecoration: todo.completed ? 'line-through' : 'none',

                cursor: 'pointer'

              }}

              onClick={() => dispatch({ type: ACTIONS.TOGGLE_TODO, payload: todo.id })}

            >

              {todo.text}

            </span>

            <button 

              onClick={() => dispatch({ type: ACTIONS.DELETE_TODO, payload: todo.id })}

            >

              Sil

            </button>

          </li>

        ))}

      </ul>

    </div>

  );

}

Özel Hooks Oluşturma

Özel hooks, stateful mantığı bileşenler arasında paylaşmanın güçlü bir yoludır. Özel hook'lar, "use" ile başlayan ve diğer hook'ları çağırabilen fonksiyonlardır.

Basit Özel Hook

// useCounter özel hook'u

function useCounter(initialValue = 0) {

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

  

  const increment = () => setCount(count + 1);

  const decrement = () => setCount(count - 1);

  const reset = () => setCount(initialValue);

  

  return { count, increment, decrement, reset };

}


// Kullanımı

function Counter() {

  const { count, increment, decrement, reset } = useCounter(10);

  

  return (

    <div>

      <p>Sayaç: {count}</p>

      <button onClick={increment}>+</button>

      <button onClick={decrement}>-</button>

      <button onClick={reset}>Sıfırla</button>

    </div>

  );

}

API Çağrısı için Özel Hook

// useApi özel hook'u

function useApi(url) {

  const [data, setData] = useState(null);

  const [loading, setLoading] = useState(true);

  const [error, setError] = useState(null);

  

  useEffect(() => {

    const fetchData = async () => {

      try {

        setLoading(true);

        setError(null);

        const response = await fetch(url);

        if (!response.ok) {

          throw new Error(`HTTP error! status: ${response.status}`);

        }

        const result = await response.json();

        setData(result);

      } catch (err) {

        setError(err.message);

      } finally {

        setLoading(false);

      }

    };

    

    fetchData();

  }, [url]);

  

  return { data, loading, error };

}


// Kullanımı

function UserProfile({ userId }) {

  const { data: user, loading, error } = useApi(`/api/users/${userId}`);

  

  if (loading) return <div>Yükleniyor...</div>;

  if (error) return <div>Hata: {error}</div>;

  if (!user) return <div>Kullanıcı bulunamadı</div>;

  

  return (

    <div>

      <h2>{user.name}</h2>

      <p>{user.email}</p>

    </div>

  );

}

Local Storage için Özel Hook

// useLocalStorage özel hook'u

function useLocalStorage(key, initialValue) {

  // State'i lazy initialization ile başlat

  const [storedValue, setStoredValue] = useState(() => {

    try {

      const item = window.localStorage.getItem(key);

      return item ? JSON.parse(item) : initialValue;

    } catch (error) {

      console.error(`Error reading localStorage key "${key}":`, error);

      return initialValue;

    }

  });

  

  // Değeri state ve localStorage'a kaydet

  const setValue = (value) => {

    try {

      // Fonksiyon olarak da değer alabilir

      const valueToStore = value instanceof Function ? value(storedValue) : value;

      setStoredValue(valueToStore);

      window.localStorage.setItem(key, JSON.stringify(valueToStore));

    } catch (error) {

      console.error(`Error setting localStorage key "${key}":`, error);

    }

  };

  

  return [storedValue, setValue];

}


// Kullanımı

function Settings() {

  const [theme, setTheme] = useLocalStorage('theme', 'light');

  const [language, setLanguage] = useLocalStorage('language', 'tr');

  

  return (

    <div>

      <label>

        Tema:

        <select value={theme} onChange={(e) => setTheme(e.target.value)}>

          <option value="light">Açık</option>

          <option value="dark">Koyu</option>

        </select>

      </label>

      <label>

        Dil:

        <select value={language} onChange={(e) => setLanguage(e.target.value)}>

          <option value="tr">Türkçe</option>

          <option value="en">English</option>

        </select>

      </label>

    </div>

  );

}

Form Yönetimi için Özel Hook

// useForm özel hook'u

function useForm(initialValues, validate) {

  const [values, setValues] = useState(initialValues);

  const [errors, setErrors] = useState({});

  const [touched, setTouched] = useState({});

  

  const handleChange = (name, value) => {

    setValues(prev => ({ ...prev, [name]: value }));

    

    // Validation

    if (validate) {

      const fieldErrors = validate({ ...values, [name]: value });

      setErrors(prev => ({ ...prev, [name]: fieldErrors[name] }));

    }

  };

  

  const handleBlur = (name) => {

    setTouched(prev => ({ ...prev, [name]: true }));

  };

  

  const handleSubmit = (onSubmit) => (e) => {

    e.preventDefault();

    

    // Tüm alanları touched olarak işaretle

    const allTouched = Object.keys(values).reduce((acc, key) => {

      acc[key] = true;

      return acc;

    }, {});

    setTouched(allTouched);

    

    // Validation

    if (validate) {

      const formErrors = validate(values);

      setErrors(formErrors);

      

      // Hata varsa submit etme

      if (Object.keys(formErrors).some(key => formErrors[key])) {

        return;

      }

    }

    

    onSubmit(values);

  };

  

  const reset = () => {

    setValues(initialValues);

    setErrors({});

    setTouched({});

  };

  

  return {

    values,

    errors,

    touched,

    handleChange,

    handleBlur,

    handleSubmit,

    reset

  };

}


// Kullanımı

function ContactForm() {

  const initialValues = {

    name: '',

    email: '',

    message: ''

  };

  

  const validate = (values) => {

    const errors = {};

    

    if (!values.name) {

      errors.name = 'İsim gerekli';

    }

    

    if (!values.email) {

      errors.email = 'E-posta gerekli';

    } else if (!/\S+@\S+\.\S+/.test(values.email)) {

      errors.email = 'Geçersiz e-posta formatı';

    }

    

    if (!values.message) {

      errors.message = 'Mesaj gerekli';

    }

    

    return errors;

  };

  

  const { values, errors, touched, handleChange, handleBlur, handleSubmit, reset } = 

    useForm(initialValues, validate);

  

  const onSubmit = (formData) => {

    console.log('Form gönderildi:', formData);

    // API çağrısı yapılabilir

    reset();

  };

  

  return (

    <form onSubmit={handleSubmit(onSubmit)}>

      <div>

        <input

          type="text"

          placeholder="İsim"

          value={values.name}

          onChange={(e) => handleChange('name', e.target.value)}

          onBlur={() => handleBlur('name')}

        />

        {touched.name && errors.name && (

          <span style={{ color: 'red' }}>{errors.name}</span>

        )}

      </div>

      

      <div>

        <input

          type="email"

          placeholder="E-posta"

          value={values.email}

          onChange={(e) => handleChange('email', e.target.value)}

          onBlur={() => handleBlur('email')}

        />

        {touched.email && errors.email && (

          <span style={{ color: 'red' }}>{errors.email}</span>

        )}

      </div>

      

      <div>

        <textarea

          placeholder="Mesaj"

          value={values.message}

          onChange={(e) => handleChange('message', e.target.value)}

          onBlur={() => handleBlur('message')}

        />

        {touched.message && errors.message && (

          <span style={{ color: 'red' }}>{errors.message}</span>

        )}

      </div>

      

      <button type="submit">Gönder</button>

      <button type="button" onClick={reset}>Temizle</button>

    </form>

  );

}

React Hooks, modern React geliştirmesinin temel taşlarıdır. useState, useEffect ve useContext gibi temel hook'ları anlamak, etkili React uygulamaları geliştirmenin anahtarıdır. Özel hook'lar ise kodunuzu daha modüler ve yeniden kullanılabilir hale getirmenin güçlü bir yoludur.