Sharing State
So far, we've always used useState inside a SINGLE component. This
lesson covers situations where more than one component needs the same
state -- and the "props drilling" problem that comes along with it.
When Two Components Need the Same State
Picture a search box (SearchBox) and a results list (ResultsList)
-- both need the SAME search text. What happens if the query state
lives inside SearchBox?
import { useState } from "react";
function SearchBox() {
const [query, setQuery] = useState("");
return (
<input
type="text"
value={query}
onChange={(event) => setQuery(event.target.value)}
placeholder="Search courses..."
/>
);
}
function ResultsList() {
// Sorun: bu component'in, kullanıcının SearchBox'a ne yazdığını bilmesi
// gerekiyor -- ama `query` state'i SearchBox'ın İÇİNDE hapsolmuş, buraya
// hiçbir şekilde ulaşamıyor.
return <p>Type in the search box above -- but this list has no way to know what you typed.</p>;
}
function SeparateStateProblemExample() {
return (
<div>
<SearchBox />
<ResultsList />
</div>
);
}
ResultsList has no way to access SearchBox's state -- each
component's own state is trapped INSIDE it; sibling components can't
see each other's state DIRECTLY.
Lifting State Up
The fix is to move the state to a place that's the COMMON ancestor of both components:
import { useState } from "react";
const courses = ["Java", "React", "Spring Boot"];
function SearchBox({ query, onQueryChange }) {
return (
<input
type="text"
value={query}
onChange={(event) => onQueryChange(event.target.value)}
placeholder="Search courses..."
/>
);
}
function ResultsList({ query }) {
const filtered = courses.filter((course) =>
course.toLowerCase().includes(query.toLowerCase()),
);
return (
<ul>
{filtered.map((course) => (
<li key={course}>{course}</li>
))}
</ul>
);
}
function LiftingStateUpExample() {
// `query` state'i artık SearchBox'ın İÇİNDE değil, ikisinin de ORTAK
// atası olan bu component'te yaşıyor -- "state'i yukarı taşımak"
// (lifting state up) dediğimiz şey bu. Her iki child da bu state'i
// props ile alıyor.
const [query, setQuery] = useState("");
return (
<div>
<SearchBox query={query} onQueryChange={setQuery} />
<ResultsList query={query} />
</div>
);
}
The query state now lives in the shared parent; both children receive
it via props -- SearchBox gets query and onQueryChange,
ResultsList gets just query. This pattern is called lifting state
up -- it's the most fundamental way to manage shared state in React.
Seeing the Same Pattern in a Different Scenario
Lifting state up isn't limited to filtering a list -- it also applies to components that show the SAME value in TWO DIFFERENT ways:
import { useState } from "react";
function SliderInput({ rating, onRatingChange }) {
return (
<input
type="range"
min="0"
max="5"
value={rating}
onChange={(event) => onRatingChange(Number(event.target.value))}
/>
);
}
function RatingDisplay({ rating }) {
return <p>Rating: {rating} / 5</p>;
}
function SyncedSiblingsExample() {
// İki farklı görünüme (bir slider, bir metin) sahip iki kardeş
// component, AYNI değeri temsil ediyor -- state, ortak ataya
// taşındığı için ikisi de her zaman senkron kalıyor.
const [rating, setRating] = useState(0);
return (
<div>
<SliderInput rating={rating} onRatingChange={setRating} />
<RatingDisplay rating={rating} />
</div>
);
}
A slider and a text display share the SAME rating value -- since the
state lives in the shared parent, when one changes, the other stays
instantly in sync.
Props Drilling: Passing Through Intermediate Layers
As the component tree gets deeper, DELIVERING a prop to the component that needs it may require the components in between to also accept that prop:
import { useState } from "react";
const courses = ["Java", "React", "Spring Boot"];
function SearchBox({ query, onQueryChange }) {
return (
<input
type="text"
value={query}
onChange={(event) => onQueryChange(event.target.value)}
placeholder="Search courses..."
/>
);
}
function ResultsList({ query }) {
const filtered = courses.filter((course) =>
course.toLowerCase().includes(query.toLowerCase()),
);
return (
<ul>
{filtered.map((course) => (
<li key={course}>{course}</li>
))}
</ul>
);
}
function ResultsPanel({ query }) {
// ResultsPanel, `query`'i KENDİSİ hiç kullanmıyor -- yalnızca
// ResultsList'e ULAŞTIRMAK için alıyor. Bu, "props drilling" (props'u
// zorunlu olarak ara katmanlardan geçirmek) dediğimiz durumun basit
// bir örneği.
return (
<div className="panel">
<ResultsList query={query} />
</div>
);
}
function PropsDrillingExample() {
const [query, setQuery] = useState("");
return (
<div>
<SearchBox query={query} onQueryChange={setQuery} />
<ResultsPanel query={query} />
</div>
);
}
ResultsPanel never uses query ITSELF -- it only accepts it to pass
along to ResultsList. This is called props drilling -- being
forced to pass a prop through intermediate layers that don't use it.
Why Props Drilling Hurts
This problem grows as the tree gets deeper:
function Level1({ user }) {
return <Level2 user={user} />;
}
function Level2({ user }) {
return <Level3 user={user} />;
}
function Level3({ user }) {
return <Level4 user={user} />;
}
function Level4({ user }) {
return <p>Logged in as {user}</p>;
}
function WhyPropsDrillingHurtsExample() {
// Level1, Level2, Level3'ün HİÇBİRİ `user`'ı kullanmıyor -- yalnızca bir
// sonraki seviyeye AKTARIYORLAR. Yalnızca en dipteki Level4 gerçekten
// kullanıyor. Ağaç derinleştikçe (ya da her seviyeye yeni prop'lar
// eklendikçe) bu, hem yazması yorucu hem de hataya açık bir hal alır --
// bir sonraki derste (Context API), bunu çözen bir yöntem göreceğiz.
return <Level1 user="Ada" />;
}
NONE of Level1, Level2, Level3 use user -- they just pass it
along. Only Level4, at the very bottom, actually uses it. Every new
level, or every new shared value, stretches this chain further --
making the code tedious to write and fragile to change. In the next
lesson (Context API), we'll see a way to solve this.
Summary and Glossary
When more than one component needs the same state, it needs to be moved to their COMMON ancestor (lifting state up) -- each child receives it via props. As the tree gets deeper, being forced to pass a prop through intermediate components that don't use it creates the "props drilling" problem -- making code tedious to write and fragile to change.
Glossary
Lifting State Up — Moving state shared by multiple components to their COMMON ancestor.
Props Drilling — Being forced to pass a prop through intermediate layer components that don't use it.