Context API
In Sharing State, we saw how props drilling becomes a problem in a deep component tree. This lesson covers React's built-in solution to that problem -- the Context API.
Creating a Context with createContext
Context is a "box" that carries a value readable from anywhere in the tree, without passing props:
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") creates a ThemeContext -- the value in the
parentheses is the DEFAULT value used when there's no Provider.
<ThemeContext.Provider value="dark"> OVERRIDES this value to "dark"
for the entire tree inside it. useContext(ThemeContext) reads the
value from the nearest Provider.
Without a Provider: The Default Value
A Provider isn't always required -- without one, useContext returns
the default value given to createContext:
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 />;
}
There's no Provider at all in this example; ThemedButton gets the
"light" value given via createContext("light"). A Provider is only
needed when you want to CHANGE that default.
Solving Props Drilling with Context
Let's rewrite the deep tree example from Sharing State, this time with Context:
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>
);
}
NONE of Level1, Level2, Level3 even know about the user prop
anymore -- only Level4, at the very bottom, reads it DIRECTLY from
UserContext. We didn't need to pass anything through the intermediate
layers.
Carrying State Inside Context
Context doesn't just carry a fixed value -- it can carry the state itself AND the function that updates it:
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 }} gives the Provider an OBJECT -- both the
current items list and the addItem function that updates it. This
is the most common way Context is used in real applications.
Wrapping Context in a Custom Hook
The custom hook pattern from the Hooks lesson pairs very naturally with Context:
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() WRAPS useContext(ThemeContext) to expose a cleaner API
-- the component that uses it calls useTheme() without ever dealing
with the concept of "context," just like a built-in hook. Throwing an
error when used outside a Provider catches misuse early.
Summary and Glossary
createContext() creates a value readable from anywhere in the tree;
a Provider OVERRIDES that value for a specific subtree; useContext()
reads the value from the nearest Provider. Without a Provider, the
default value given to createContext is used. Context can carry a
fixed value, or state together with the functions that update it --
combined with wrapping useContext in a custom hook (like useTheme()),
this is the most common way to manage what could be called "global"
state in React.
Glossary
Context — A React structure that carries a value readable from anywhere in the tree, without passing props.
Provider — The component that sets a Context's value for the entire subtree beneath it.
Practical Project
There's a real, runnable example project that brings together the concepts from this category (Sharing State, Context API): State Management Demo -- a course list that can be filtered by search and have courses added to favorites.
It shows keeping the search text in a shared parent (lifting state up)
and passing it via props to both the search box and results list, and
managing favorite courses with a FavoritesContext
(createContext + Provider + custom hook) read from different parts of
the tree (the course list AND a favorites counter in the header) with
NO props drilling at all, all working together. You can download it
and run it yourself, and read through the code line by line:
git clone https://github.com/cdurgun/react-course-projects.git
cd react-course-projects
npm install
cd projects/state-management
npm run dev
The react-course-projects repo uses npm workspaces -- npm install
only needs to run once, at the repo root, and every project folder
shares the same dependencies (no separate node_modules per folder). If
you've already run npm install at the root, you can just
cd react-course-projects/projects/state-management and run
npm run dev.