Sharing State

Situations where more than one component needs the same state -- lifting state up and the props drilling problem, with simple examples.

Intermediate 11 min
TR

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.