Context API

React'in props drilling'e yerleşik çözümü Context API -- createContext, useContext, Provider, ve context'i bir custom hook'a sarmalamak, basit örneklerle.

Orta 13 dk
EN

Context API

Sharing State dersinde, derin bir component ağacında props drilling'in nasıl bir soruna dönüştüğünü gördük. Bu ders, React'in bu soruna yerleşik çözümünü -- Context API'yi -- anlatıyor.

createContext ile Bir Context Oluşturmak

Context, ağacın herhangi bir yerinden, props geçirmeden okunabilecek bir değer taşıyan bir "kutu"dur:

import { createContext, useContext } from "react";

// createContext(), bir "kutu" oluşturur -- ağacın herhangi bir yerinden,
// props geçirmeden okunabilecek bir değer taşır. Parantez içindeki "light",
// bir Provider bulunmadığında kullanılacak VARSAYILAN değer.
const ThemeContext = createContext("light");

function ThemedButton() {
  // useContext(ThemeContext), en yakın ThemeContext.Provider'ın
  // `value`'sunu okur -- ThemedButton, bu değeri props ile ALMADAN
  // doğrudan buradan okuyor.
  const theme = useContext(ThemeContext);

  return <button className={theme}>Click me</button>;
}

function CreateContextExample() {
  return (
    <ThemeContext.Provider value="dark">
      <ThemedButton />
    </ThemeContext.Provider>
  );
}

createContext("light"), bir ThemeContext oluşturur -- parantez içindeki değer, bir Provider bulunmadığında kullanılacak VARSAYILAN değerdir. <ThemeContext.Provider value="dark">, içindeki tüm ağaç için bu değeri "dark" olarak EZER (override eder). useContext(ThemeContext), en yakın Provider'ın değerini okur.

Provider Yokken: Varsayılan Değer

Provider her zaman gerekli değil -- bir Provider yoksa, useContext createContext'e verilen varsayılan değeri döner:

import { createContext, useContext } from "react";

const ThemeContext = createContext("light");

function ThemedButton() {
  const theme = useContext(ThemeContext);
  return <button className={theme}>Click me</button>;
}

function DefaultValueExample() {
  // Burada HİÇ Provider yok -- ThemedButton, useContext(ThemeContext)
  // çağırdığında, createContext("light") ile verilen VARSAYILAN değeri
  // ("light") alır. Provider yalnızca, o değeri EZMEK (override etmek)
  // istediğimizde gerekli.
  return <ThemedButton />;
}

Bu örnekte hiç Provider yok; ThemedButton, createContext("light") ile verilen "light" değerini alıyor. Provider, yalnızca o varsayılanı DEĞİŞTİRMEK istediğinde gerekiyor.

Props Drilling'i Context ile Çözmek

Sharing State dersindeki derin ağaç örneğini, bu sefer Context ile yeniden yazalım:

import { createContext, useContext } from "react";

const UserContext = createContext(null);

function Level1() {
  return <Level2 />;
}

function Level2() {
  return <Level3 />;
}

function Level3() {
  return <Level4 />;
}

function Level4() {
  // Sharing State dersindeki örneğin aksine, Level1/Level2/Level3'ün
  // HİÇBİRİ `user` prop'unu bilmiyor bile -- yalnızca Level4, en dipte,
  // UserContext'ten DOĞRUDAN okuyor.
  const user = useContext(UserContext);
  return <p>Logged in as {user}</p>;
}

function AvoidingPropsDrillingExample() {
  return (
    <UserContext.Provider value="Ada">
      <Level1 />
    </UserContext.Provider>
  );
}

Level1, Level2, Level3'ün HİÇBİRİ artık user prop'unu bilmiyor bile -- yalnızca en dipteki Level4, UserContext'ten DOĞRUDAN okuyor. Ara katmanlardan hiçbir şey geçirmemize gerek kalmadı.

Context İçinde State Taşımak

Context yalnızca sabit bir değer taşımaz -- state'in kendisini VE onu güncelleyen fonksiyonu birlikte taşıyabilir:

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

const CartContext = createContext(null);

function AddItemButton() {
  const { addItem } = useContext(CartContext);
  return <button onClick={() => addItem("Course")}>Add to Cart</button>;
}

function CartCount() {
  const { items } = useContext(CartContext);
  return <p>Items in cart: {items.length}</p>;
}

function ContextWithStateExample() {
  // Context yalnızca SABİT bir değer taşımaz -- burada `value`'ya bir
  // nesne veriyoruz: hem state'in kendisi (`items`) hem de onu
  // GÜNCELLEYEN fonksiyon (`addItem`). Bu, gerçek uygulamalarda en sık
  // görülen Context kullanımı.
  const [items, setItems] = useState([]);

  function addItem(item) {
    setItems([...items, item]);
  }

  return (
    <CartContext.Provider value={{ items, addItem }}>
      <AddItemButton />
      <CartCount />
    </CartContext.Provider>
  );
}

value={{ items, addItem }} ile Provider'a bir NESNE veriyoruz -- hem güncel items listesini hem de onu güncelleyen addItem fonksiyonunu. Bu, gerçek uygulamalarda en sık görülen Context kullanım biçimi.

Context'i Bir Custom Hook'a Sarmalamak

Hooks dersindeki custom hook deseni, Context ile birlikte çok yaygın kullanılan bir kalıp oluşturuyor:

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

const ThemeContext = createContext(null);

function ThemeProvider({ children }) {
  const [theme, setTheme] = useState("light");

  return (
    <ThemeContext.Provider value={{ theme, setTheme }}>
      {children}
    </ThemeContext.Provider>
  );
}

// Custom Hooks dersindeki desen burada devam ediyor: useContext'i
// component'lerin içine dağıtmak yerine, ayrı bir hook'a (`useTheme`)
// sarmalıyoruz. Böylece kullanan component, "context" kavramıyla hiç
// uğraşmadan `useTheme()` çağırıyor -- tıpkı `useState` gibi.
function useTheme() {
  const context = useContext(ThemeContext);
  if (!context) {
    throw new Error("useTheme must be used within a ThemeProvider");
  }
  return context;
}

function ThemeToggleButton() {
  const { theme, setTheme } = useTheme();

  return (
    <button onClick={() => setTheme(theme === "light" ? "dark" : "light")}>
      Current theme: {theme}
    </button>
  );
}

function CustomContextHookExample() {
  return (
    <ThemeProvider>
      <ThemeToggleButton />
    </ThemeProvider>
  );
}

useTheme(), useContext(ThemeContext)'i SARMALAYIP daha temiz bir API sunuyor -- kullanan component, "context" kavramıyla hiç uğraşmadan useTheme() çağırıyor, tıpkı yerleşik bir hook gibi. Provider dışında kullanılırsa hata fırlatması, yanlış kullanımı erken yakalamamızı sağlıyor.

Özet ve Terimler Sözlüğü

createContext(), ağacın herhangi bir yerinden okunabilecek bir değer oluşturur; Provider, bu değeri belirli bir alt ağaç için EZER; useContext(), en yakın Provider'ın değerini okur. Provider yoksa, createContext'e verilen varsayılan değer kullanılır. Context, sabit bir değer taşıyabildiği gibi, state VE onu güncelleyen fonksiyonları birlikte de taşıyabilir -- bu, useContext'i bir custom hook'a sarmalamakla (useTheme() gibi) birleştiğinde, React'te "global" sayılabilecek state'i yönetmenin en yaygın yoludur.

Terimler Sözlüğü

Context — Ağacın herhangi bir yerinden, props geçirmeden okunabilen bir değer taşıyan React yapısı.

Provider — Bir Context'in değerini, kendi altındaki tüm ağaç için belirleyen component.

Pratik Proje

Bu kategoride (Sharing State, Context API) öğrendiğimiz kavramları bir arada kullanan, gerçek ve çalıştırılabilir bir örnek proje var: State Management Demo -- arama ile filtrelenebilen, favorilere eklenebilen bir kurs listesi uygulaması.

Proje; arama metnini ortak parent'ta tutup (lifting state up) hem arama kutusuna hem sonuç listesine props ile aktarmayı, favori kursları bir FavoritesContext ile (createContext + Provider + custom hook) HİÇBİR props drilling olmadan ağacın farklı yerlerinden (kurs listesi VE başlıktaki favori sayacı) okumayı bir arada gösteriyor. Bilgisayarına indirip çalıştırabilir, kodunu satır satır inceleyebilirsin:

git clone https://github.com/cdurgun/react-course-projects.git
cd react-course-projects
npm install
cd projects/state-management
npm run dev

react-course-projects deposu npm workspaces kullanır -- npm install yalnızca bir kez, depo kökünde çalıştırılır ve tüm proje klasörleri ortak bağımlılıkları paylaşır (her klasörde ayrı bir node_modules oluşmaz). Kök dizinde npm install'ı daha önce çalıştırdıysan, doğrudan cd react-course-projects/projects/state-management yapıp npm run dev demen yeterli.