State'i Korumak ve Sıfırlamak

State bileşenler arasında izole edilmiştir. React, kullanıcı arayüzü (UI) ağacındaki yerlerine göre hangi state’in hangi bileşene ait olduğunu takip eder. Yeniden render’lar arasında state’in ne zaman korunacağını ve ne zaman sıfırlanacağını kontrol edebilirsiniz.

Bunları öğreneceksiniz

  • React bileşen yapılarını nasıl “görür”
  • React state’i korumayı ya da sıfırlamaya ne zaman seçer
  • React bileşenin state’ini sıfırlamaya nasıl zorlanır
  • Anahtarlar ve tipler state’in korunup korunmamasını nasıl etkiler

Kullanıcı arayüzü (UI) ağacı

Tarayıcılar, kullanıcı arayüzünü modellemek için pek çok ağaç yapısı kullanırlar. DOM HTML elementlerini temsil eder, CSSOM aynı şeyi CSS için yapar. Bir Erişilebilirlik ağacı bile var!

React, oluşturduğunuz kullanıcı arayüzünü yönetmek ve modellek için de ağaç yapılarını kullanır. React, JSX’inizden kullanıcı arayüzü ağaçları oluşturur. Ardından React DOM, tarayıcı DOM elementlerini güncelleyerek kullanıcı arayüzü ağacı ile eşleşmesini sağlar. (React Native bu ağaçları mobil platformlara özgü elementlere çevirir.)

Yatay olarak düzenlenmiş üç bölümden oluşan diyagram. İlk bölümde, 'A Bileşeni', 'B bileşeni' ve 'C Bileşeni' etiketli dikey olarak istiflenmiş üç dikdörtgen vardır. Bir sonraki bölüme geçişi gösteren 'React' etiketli ve üstünde React logosu olan bir ok vardır. Orta bölümde bir bileşen ağacı vardır. Kök 'A' olarak ve iki alt eleman 'B' ve 'C' etiketlidir. Bir sonraki bölüme geçiş yine 'React' etiketli ve üstüne React logosu olan bir okla gösterilmiştir. Üçüncü ve son bölüm ise yalnızca bir alt kümenin vurgulandığı (orta bölümden alt ağacı gösteren) 8 node'dan oluşan bir ağaç gösteren tarayıcı wireframe'idir.
Yatay olarak düzenlenmiş üç bölümden oluşan diyagram. İlk bölümde, 'A Bileşeni', 'B bileşeni' ve 'C Bileşeni' etiketli dikey olarak istiflenmiş üç dikdörtgen vardır. Bir sonraki bölüme geçişi gösteren 'React' etiketli ve üstünde React logosu olan bir ok vardır. Orta bölümde bir bileşen ağacı vardır. Kök 'A' olarak ve iki alt eleman 'B' ve 'C' etiketlidir. Bir sonraki bölüme geçiş yine 'React' etiketli ve üstüne React logosu olan bir okla gösterilmiştir. Üçüncü ve son bölüm ise yalnızca bir alt kümenin vurgulandığı (orta bölümden alt ağacı gösteren) 8 node'dan oluşan bir ağaç gösteren tarayıcı wireframe'idir.

React, bileşenlerden, React DOM’un DOM’u render etmek için kullandığı bir kullanıcı arayüzü (UI) ağacı oluşturur

State ağaçtaki bir konuma bağlıdır

React arayüzünüzdeki bileşen yapısı için render ağaçları inşa eder.

Bir bileşene state verdiğinizde, state’in bileşen içinde “yaşadığını” düşünebilirsiniz. Aslında state, React içinde tutulur. React tuttuğu her bir state parçasını, bileşenin kullanıcı arayüzü ağacında bulunduğu yere göre doğru bileşenle ilişkilendirir.

Örneğin burada yalnızca bir <Counter /> JSX elemanı vardır, ancak bu eleman iki farklı konumda render edilir:

import { useState } from 'react';

export default function App() {
  const counter = <Counter />;
  return (
    <div>
      {counter}
      {counter}
    </div>
  );
}

function Counter() {
  const [score, setScore] = useState(0);
  const [hover, setHover] = useState(false);

  let className = 'counter';
  if (hover) {
    className += ' hover';
  }

  return (
    <div
      className={className}
      onPointerEnter={() => setHover(true)}
      onPointerLeave={() => setHover(false)}
    >
      <h1>{score}</h1>
      <button onClick={() => setScore(score + 1)}>
        Bir ekle 
      </button>
    </div>
  );
}

Bunlar ağaç olarak nasıl görünüyor:

React bileşenleri ağacının diyagramı. Kök node 'div' etiketli ve iki alt elemana sahip. Alt elemanların her ikisi de 'Counter' etiketli ve 0 değerine eşit 'count' etiketli bir state baloncuğu içerir.
React bileşenleri ağacının diyagramı. Kök node 'div' etiketli ve iki alt elemana sahip. Alt elemanların her ikisi de 'Counter' etiketli ve 0 değerine eşit 'count' etiketli bir state baloncuğu içerir.

React ağacı

Bu sayaçlar iki farklı sayaçtır çünkü her ikisi de ağaçta kendi konumunda render edilir. Genellikle React’i kullanmak için bu konumları düşünmeniz gerekmez, ancak çalışma mantığını anlamak faydalı olabilir.

React’te, ekrandaki her bileşenin içindeki state’ler izole bir şekildedir. Örneğin, iki Counter bileşenini yan yana render ederseniz, her birinin kendi bağımsız score ve hover state’leri olacaktır.

Her iki sayaca da tıklayın ve birbirlerini etkilemediklerini görün:

import { useState } from 'react';

export default function App() {
  return (
    <div>
      <Counter />
      <Counter />
    </div>
  );
}

function Counter() {
  const [score, setScore] = useState(0);
  const [hover, setHover] = useState(false);

  let className = 'counter';
  if (hover) {
    className += ' hover';
  }

  return (
    <div
      className={className}
      onPointerEnter={() => setHover(true)}
      onPointerLeave={() => setHover(false)}
    >
      <h1>{score}</h1>
      <button onClick={() => setScore(score + 1)}>
        Bir ekle 
      </button>
    </div>
  );
}

Gördüğünüz gibi, bir sayaç güncellendiği zaman sadece o bileşenin state’i güncellenmektedir:

React bileşenleri ağacının diyagramı. Kök node 'div' etiketli ve iki alt elemana sahiptir. Soldaki alt eleman 'Counter' etiketli ve 0 değerine eşit 'count' etiketli bir state baloncuğu içerir. Sağdaki alt eleman 'Counter' etiketli ve 1 değerine eşit 'count' etiketli bir state baloncuğu içerir. Sağdaki alt elemanın state baloncuğu, değerinin güncellendiğini belirtmek için sarı renkle vurgulanmış.
React bileşenleri ağacının diyagramı. Kök node 'div' etiketli ve iki alt elemana sahiptir. Soldaki alt eleman 'Counter' etiketli ve 0 değerine eşit 'count' etiketli bir state baloncuğu içerir. Sağdaki alt eleman 'Counter' etiketli ve 1 değerine eşit 'count' etiketli bir state baloncuğu içerir. Sağdaki alt elemanın state baloncuğu, değerinin güncellendiğini belirtmek için sarı renkle vurgulanmış.

State’i güncelleme

React, aynı bileşeni aynı konumda render ettiğiniz sürece state’i koruyacaktır. Bunu görmek için her iki sayacı da artırın, ardından “İkinci sayacı render et” kutucuğunun işaretini kaldırarak ikinci bileşeni render etmeyi bırakın ve ardından kutucuğu tekrar işaretleyerek bileşeni yeniden render edin:

import { useState } from 'react';

export default function App() {
  const [showB, setShowB] = useState(true);
  return (
    <div>
      <Counter />
      {showB && <Counter />} 
      <label>
        <input
          type="checkbox"
          checked={showB}
          onChange={e => {
            setShowB(e.target.checked)
          }}
        />
        İkinci sayacı render et
      </label>
    </div>
  );
}

function Counter() {
  const [score, setScore] = useState(0);
  const [hover, setHover] = useState(false);

  let className = 'counter';
  if (hover) {
    className += ' hover';
  }

  return (
    <div
      className={className}
      onPointerEnter={() => setHover(true)}
      onPointerLeave={() => setHover(false)}
    >
      <h1>{score}</h1>
      <button onClick={() => setScore(score + 1)}>
        Bir ekle
      </button>
    </div>
  );
}

İkinci sayacı render etmeyi bıraktığınız anda state’in nasıl tamamen kaybolduğuna dikkat edin. Bunun nedeni, React’in bir bileşeni render etmeyi bıraktığı zaman o bileşenin state’ini yok etmesidir.

React bileşenleri ağacının diyagramı. Kök node 'div' etiketli ve iki alt elemana sahip. Soldaki alt eleman 'Counter' etiketli ve 0 değerine eşit 'count' etiketli bir state baloncuğu içerir. Sağdaki alt eleman eksik ve onun yerine, ağaçtan silinmekte olan bileşeni gösteren sarı bir 'puf' resmi var.
React bileşenleri ağacının diyagramı. Kök node 'div' etiketli ve iki alt elemana sahip. Soldaki alt eleman 'Counter' etiketli ve 0 değerine eşit 'count' etiketli bir state baloncuğu içerir. Sağdaki alt eleman eksik ve onun yerine, ağaçtan silinmekte olan bileşeni gösteren sarı bir 'puf' resmi var.

Bileşeni silme

”İkinci sayacı render et” kutucuğunu işaretlediğinizde, ikinci bir Counter bileşeni ve state’i sıfırdan oluşturulur (score = 0) ve DOM’a eklenir.

React bileşenleri ağacının diyagramı. Kök node 'div' etiketli ve iki alt elemana sahip. Soldaki alt eleman 'Counter' etiketli ve 0 değerine eşit 'count' etiketli bir state baloncuğu içerir. Sağdaki alt eleman 'Counter' etiketli ve 0 değerine eşit 'count' etiketli bir state baloncuğu içerir. Sağdaki alt eleman node'unun tamamı, ağaca yeni eklendiğini göstermek için sarı renkle vurgulanmış.
React bileşenleri ağacının diyagramı. Kök node 'div' etiketli ve iki alt elemana sahip. Soldaki alt eleman 'Counter' etiketli ve 0 değerine eşit 'count' etiketli bir state baloncuğu içerir. Sağdaki alt eleman 'Counter' etiketli ve 0 değerine eşit 'count' etiketli bir state baloncuğu içerir. Sağdaki alt eleman node'unun tamamı, ağaca yeni eklendiğini göstermek için sarı renkle vurgulanmış.

Bileşen ekleme

React, aynı bileşeni kullanıcı arayüzü ağacında aynı konumda render ettiğiniz sürece state’i koruyacaktır. Bileşen kaldırılırsa ya da aynı konumda başka bir bileşen render edilirse, React state’i yok edecektir.

Aynı konumdaki aynı bileşen state’i korur

Bu örnekte iki farklı <Counter /> elemanı var:

import { useState } from 'react';

export default function App() {
  const [isFancy, setIsFancy] = useState(false);
  return (
    <div>
      {isFancy ? (
        <Counter isFancy={true} /> 
      ) : (
        <Counter isFancy={false} /> 
      )}
      <label>
        <input
          type="checkbox"
          checked={isFancy}
          onChange={e => {
            setIsFancy(e.target.checked)
          }}
        />
        Süslü (fancy) stili kullan
      </label>
    </div>
  );
}

function Counter({ isFancy }) {
  const [score, setScore] = useState(0);
  const [hover, setHover] = useState(false);

  let className = 'counter';
  if (hover) {
    className += ' hover';
  }
  if (isFancy) {
    className += ' fancy';
  }

  return (
    <div
      className={className}
      onPointerEnter={() => setHover(true)}
      onPointerLeave={() => setHover(false)}
    >
      <h1>{score}</h1>
      <button onClick={() => setScore(score + 1)}>
        Bir ekle
      </button>
    </div>
  );
}

Kutucuğu işaretlediğinizde veya işareti kaldırdığınızda, sayacın state’i sıfırlanmaz. isFancy ister true ister false olsun, kök App bileşeninden döndürülen div’in ilk alt elemanı her zaman bir <Counter /> bileşenidir:

Aralarındaki bir okla ayrılmış iki bölümden oluşan diyagram. Her bölüm, isFancy etiketli bir state baloncuğu içeren 'App' etiketli bir üst elemana sahip bileşen düzeni içerir. Bu bileşenin 'div' etiketli bir alt elemanı vardır ve bu tek alt elemana aktarılan isFancy değerini (mor renkle vurgulanmış) içeren bir prop baloncuğu içerir. Son alt eleman 'Counter' etiketli ve her iki diyagramda da 3 değerine eşit 'counter' etiketli bir state baloncuğu içerir. Diyagramın sol bölümünde hiçbir şey vurgulanmamış ve isFancy üst eleman state değeri false'tur. Diyagramın sağ bölümünde, isFancy üst eleman state değeri true olarak değişmiş ve sarı renkle vurgulanmıştır. Aynı zamanda isFancy değeri değişen prop baloncuğu da sarı renkle vurgulanmıştır.
Aralarındaki bir okla ayrılmış iki bölümden oluşan diyagram. Her bölüm, isFancy etiketli bir state baloncuğu içeren 'App' etiketli bir üst elemana sahip bileşen düzeni içerir. Bu bileşenin 'div' etiketli bir alt elemanı vardır ve bu tek alt elemana aktarılan isFancy değerini (mor renkle vurgulanmış) içeren bir prop baloncuğu içerir. Son alt eleman 'Counter' etiketli ve her iki diyagramda da 3 değerine eşit 'counter' etiketli bir state baloncuğu içerir. Diyagramın sol bölümünde hiçbir şey vurgulanmamış ve isFancy üst eleman state değeri false'tur. Diyagramın sağ bölümünde, isFancy üst eleman state değeri true olarak değişmiş ve sarı renkle vurgulanmıştır. Aynı zamanda isFancy değeri değişen prop baloncuğu da sarı renkle vurgulanmıştır.

App state’inin güncellenmesi Counter’ı sıfırlamaz çünkü Counter aynı konumda kalmaktadır

Bu bileşen, aynı konumdaki aynı bileşendir. Bu nedenle React’in bakış açısından aynı sayaçtır.

Tuzak

React için önemli olanın JSX işaretlemesindeki (markup) değil, kullanıcı arayüzü ağacındaki konumun olduğunu unutmayın! Bu bileşen, if koşulu içinde ve dışında farklı <Counter /> JSX elemanlarıyla iki farklı return ifadesine sahiptir:

import { useState } from 'react';

export default function App() {
  const [isFancy, setIsFancy] = useState(false);
  if (isFancy) {
    return (
      <div>
        <Counter isFancy={true} />
        <label>
          <input
            type="checkbox"
            checked={isFancy}
            onChange={e => {
              setIsFancy(e.target.checked)
            }}
          />
          Süslü (fancy) stili kullan
        </label>
      </div>
    );
  }
  return (
    <div>
      <Counter isFancy={false} />
      <label>
        <input
          type="checkbox"
          checked={isFancy}
          onChange={e => {
            setIsFancy(e.target.checked)
          }}
        />
        Süslü (fancy) stili kullan
      </label>
    </div>
  );
}

function Counter({ isFancy }) {
  const [score, setScore] = useState(0);
  const [hover, setHover] = useState(false);

  let className = 'counter';
  if (hover) {
    className += ' hover';
  }
  if (isFancy) {
    className += ' fancy';
  }

  return (
    <div
      className={className}
      onPointerEnter={() => setHover(true)}
      onPointerLeave={() => setHover(false)}
    >
      <h1>{score}</h1>
      <button onClick={() => setScore(score + 1)}>
        Bir ekle
      </button>
    </div>
  );
}

Kutucuğa tıkladığınız zaman state’in sıfırlanmasını bekliyor olabilirsiniz ancak state sıfırlanmıyor! Bunun nedeni her iki <Counter /> elemanı da aynı konumda render edilmektedir. React, fonksiyonunuzda koşullu ifadeleri nereye koyduğunuzu bilmez. React’in tüm “gördüğü” döndürdüğünüz ağaçtır.

Her iki durumda da, App bileşeni ilk alt eleman olarak <Counter /> bileşenini içeren bir <div> döndürür. React’e göre, bu iki sayaç da aynı “adrese” sahiptir: kökün ilk alt elemanının ilk alt elemanı. React, mantığınızı nasıl yapılandırdığınıza bakmaksızın bunları önceki ve sonraki render’lar arasında bu şekilde eşleştirir.

Aynı konumdaki farklı bileşenler state’i sıfırlar

Bu örnekte kutucuğa tıkalamak, <Counter> bileşenini <p> ile değiştirecektir:

import { useState } from 'react';

export default function App() {
  const [isPaused, setIsPaused] = useState(false);
  return (
    <div>
      {isPaused ? (
        <p>Sonra görüşürüz!</p> 
      ) : (
        <Counter /> 
      )}
      <label>
        <input
          type="checkbox"
          checked={isPaused}
          onChange={e => {
            setIsPaused(e.target.checked)
          }}
        />
        Mola ver
      </label>
    </div>
  );
}

function Counter() {
  const [score, setScore] = useState(0);
  const [hover, setHover] = useState(false);

  let className = 'counter';
  if (hover) {
    className += ' hover';
  }

  return (
    <div
      className={className}
      onPointerEnter={() => setHover(true)}
      onPointerLeave={() => setHover(false)}
    >
      <h1>{score}</h1>
      <button onClick={() => setScore(score + 1)}>
        Bir ekle
      </button>
    </div>
  );
}

Burada, aynı konumda farklı bileşen tipleri arasında geçiş yapmaktayız. Başlangıçta, <div> elementinin ilk alt elemanı bir Counter içermekteydi. Ancak bunu bir p ile değiştirdiğinizde React, Counter’ı kullanıcı arayüzü ağacından kaldırır ve state’ini yok eder.

Üç bölümden oluşan ve bölümler arasında okla geçişi gösteren bir diyagram. İlk bölüm, 3 değerine sahip 'count' etiketli bir state baloncuğu içeren 'Counter' etiketli tek bir alt elemana sahip 'div' etiketli bir React bileşeni içerir. Orta bölüm aynı 'div' üst elemanına sahiptir ancak alt bileşen silinmiştir ve sarı bir 'puf' resmiyle gösterilmiştir. Üçüncü bölümde de aynı 'div' üst elemanı vardır ama sarı renkle vurgulanmış 'p' etiketli yeni bir alt eleman içermektedir.
Üç bölümden oluşan ve bölümler arasında okla geçişi gösteren bir diyagram. İlk bölüm, 3 değerine sahip 'count' etiketli bir state baloncuğu içeren 'Counter' etiketli tek bir alt elemana sahip 'div' etiketli bir React bileşeni içerir. Orta bölüm aynı 'div' üst elemanına sahiptir ancak alt bileşen silinmiştir ve sarı bir 'puf' resmiyle gösterilmiştir. Üçüncü bölümde de aynı 'div' üst elemanı vardır ama sarı renkle vurgulanmış 'p' etiketli yeni bir alt eleman içermektedir.

Counter, p ile değiştiğinde, Counter silinir ve p eklenir

Üç bölümden oluşan ve bölümler arasında okla geçişi gösteren bir diyagram. İlk bölüm 'p' etiketli bir React bileşeni içerir. Orta bölüm aynı 'div' üst elemanına sahiptir ancak alt bileşen silinmiştir ve sarı bir 'puf' resmiyle gösterilmiştir. Üçüncü bölüm yine aynı 'div' üst elemanına sahiptir ancak şimdi sarı ile vurgulanmış 0 değerine sahip 'count' etiketli state baloncuğu içeren 'Counter' etikletli yeni bir alt eleman içerir.
Üç bölümden oluşan ve bölümler arasında okla geçişi gösteren bir diyagram. İlk bölüm 'p' etiketli bir React bileşeni içerir. Orta bölüm aynı 'div' üst elemanına sahiptir ancak alt bileşen silinmiştir ve sarı bir 'puf' resmiyle gösterilmiştir. Üçüncü bölüm yine aynı 'div' üst elemanına sahiptir ancak şimdi sarı ile vurgulanmış 0 değerine sahip 'count' etiketli state baloncuğu içeren 'Counter' etikletli yeni bir alt eleman içerir.

Geri geçiş yaparken, p silinir ve Counter eklenir

Aynı zamanda, aynı konumda farklı bir bileşen render ettiğinizde, tüm alt ağacın (subtree) state’ini sıfırlar. Nasıl çalıştığını görmek için sayacı artırın ve kutucuğu işaretleyin:

import { useState } from 'react';

export default function App() {
  const [isFancy, setIsFancy] = useState(false);
  return (
    <div>
      {isFancy ? (
        <div>
          <Counter isFancy={true} /> 
        </div>
      ) : (
        <section>
          <Counter isFancy={false} />
        </section>
      )}
      <label>
        <input
          type="checkbox"
          checked={isFancy}
          onChange={e => {
            setIsFancy(e.target.checked)
          }}
        />
        Süslü (fancy) stili kullan
      </label>
    </div>
  );
}

function Counter({ isFancy }) {
  const [score, setScore] = useState(0);
  const [hover, setHover] = useState(false);

  let className = 'counter';
  if (hover) {
    className += ' hover';
  }
  if (isFancy) {
    className += ' fancy';
  }

  return (
    <div
      className={className}
      onPointerEnter={() => setHover(true)}
      onPointerLeave={() => setHover(false)}
    >
      <h1>{score}</h1>
      <button onClick={() => setScore(score + 1)}>
        Bir ekle
      </button>
    </div>
  );
}

Sayaç state’i kutucuğa tıkladığınız zaman sıfırlanır. Counter render etmenize rağmen, div’in ilk alt elemanı div’den section’a dönüşür. Alt eleman olan div DOM’dan kaldırıldığında, altındaki ağacın tamamı da (Counter ve state’i de dahil olmak üzere) yok edilir.

Üç bölümden oluşan ve bölümler arasında okla geçişi gösteren bir diyagram. İlk bölüm, 'section' etiketli tek bir alt elemana sahip 'div' etkietli bir React bileşeni içerir. Bu bileşen 3 değerine eşit 'count' etiketli bir state baloncuğu içeren 'Counter' etiketli tek bir alt elemana sahiptir. Orta bölüm aynı 'div' üst elemanına sahiptir, ancak alt bileşenler silinmiştir ve sarı bir 'puf' resmiyle gösterilmiştir. Üçüncü bölüm de aynı 'div'  üst elemanına sahiptir, 'div' etiketli yeni alt eleman sarı renkle vurgulanmış ve ayrıca 'Counter' etiketli yeni alt eleman 0 değerine eşit 'count' etiketli bir state baloncuğuna sahiptir ve hepsi sarı ile vurgulanmıştır.
Üç bölümden oluşan ve bölümler arasında okla geçişi gösteren bir diyagram. İlk bölüm, 'section' etiketli tek bir alt elemana sahip 'div' etkietli bir React bileşeni içerir. Bu bileşen 3 değerine eşit 'count' etiketli bir state baloncuğu içeren 'Counter' etiketli tek bir alt elemana sahiptir. Orta bölüm aynı 'div' üst elemanına sahiptir, ancak alt bileşenler silinmiştir ve sarı bir 'puf' resmiyle gösterilmiştir. Üçüncü bölüm de aynı 'div'  üst elemanına sahiptir, 'div' etiketli yeni alt eleman sarı renkle vurgulanmış ve ayrıca 'Counter' etiketli yeni alt eleman 0 değerine eşit 'count' etiketli bir state baloncuğuna sahiptir ve hepsi sarı ile vurgulanmıştır.

section, div’le değiştiğinde, section silinir ve yerine yeni div eklenir

Üç bölümden oluşan ve bölümler arasında okla geçişi gösteren bir diyagram. İlk bölüm, 'div' etiketli tek bir alt elemana sahip 'div' etiketli bir React bileşeni içerir. Bu bileşen, 0 değerine eşit 'count' etiketli bir state baloncuğu içeren 'Counter' etiketli tek bir alt elemana sahiptir. Orta bölüm aynı 'div' üst elemanına sahiptir, ancak alt bileşenler silinmiştir ve sarı bir 'puf' resmiyle gösterilmiştir. Üçüncü bölüm yine aynı 'div' üst elemanına sahiptir ve şimdi 0 değerine eşit 'count' etiketli state baloncuğu içeren 'Counter' etiketli bir alt eleman içeren 'section' etiketli bir alt elemana sahiptir ve hepsi sarı ile vurgulanmıştır.
Üç bölümden oluşan ve bölümler arasında okla geçişi gösteren bir diyagram. İlk bölüm, 'div' etiketli tek bir alt elemana sahip 'div' etiketli bir React bileşeni içerir. Bu bileşen, 0 değerine eşit 'count' etiketli bir state baloncuğu içeren 'Counter' etiketli tek bir alt elemana sahiptir. Orta bölüm aynı 'div' üst elemanına sahiptir, ancak alt bileşenler silinmiştir ve sarı bir 'puf' resmiyle gösterilmiştir. Üçüncü bölüm yine aynı 'div' üst elemanına sahiptir ve şimdi 0 değerine eşit 'count' etiketli state baloncuğu içeren 'Counter' etiketli bir alt eleman içeren 'section' etiketli bir alt elemana sahiptir ve hepsi sarı ile vurgulanmıştır.

Geri geçiş yaparken, div silinir ve section eklenir

Genel bir kural olarak, yeniden render’lar arasında state’i korumak istiyorsanız, ağacınızın yapısının render’lar arasında “eşleşmesi” gerekmektedir. Eğer yapı farklıysa, state yok edilecektir çünkü React bileşeni ağaçtan çıkardığında o bileşenin state’ini yok eder.

Tuzak

Bu nedenle bileşen fonksiyonu tanımlarını iç içe yapmamalısınız.

Burada, MyTextField bileşen fonksiyonu MyComponent içinde tanımlanmıştır:

import { useState } from 'react';

export default function MyComponent() {
  const [counter, setCounter] = useState(0);

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

    return (
      <input
        value={text}
        onChange={e => setText(e.target.value)}
      />
    );
  }

  return (
    <>
      <MyTextField />
      <button onClick={() => {
        setCounter(counter + 1)
      }}>{counter} defa tıklandı</button>
    </>
  );
}

Butona her tıkladığınızda, input elemanının state’i kaybolmaktadır! Bunun nedeni, MyComponent bileşeni her render edildiğinde farklı bir MyTextField fonksiyonu oluşturulmaktadır. Aynı konumda farklı bir bileşen oluşturuyorsunuz, bu nedenle React altındaki tüm state’leri sıfırlar. Bu durum, hatalara ve performans sorunlarına yol açar. Bu problemden kaçınmak için, bileşen fonksiyonlarını en üstte tanımlayın ve tanımları iç içe yapmayın.

Aynı konumda state’i sıfırlamak

Varsayılan olarak React, aynı konumda kalan bir bileşenin state’ini korur. Genellikle istediğimiz davranış budur ve bu yüzden varsayılan olarak böyle davranmaktadır. Ancak bazen bir bileşenin state’ini sıfırlamak isteyebilirsiniz. İki oyuncunun her turdaki puanlarını takip etmesine izin veren bu uygulamayı ele alalım:

import { useState } from 'react';

export default function Scoreboard() {
  const [isPlayerA, setIsPlayerA] = useState(true);
  return (
    <div>
      {isPlayerA ? (
        <Counter person="Taylor" />
      ) : (
        <Counter person="Sarah" />
      )}
      <button onClick={() => {
        setIsPlayerA(!isPlayerA);
      }}>
        Sonraki oyuncu!
      </button>
    </div>
  );
}

function Counter({ person }) {
  const [score, setScore] = useState(0);
  const [hover, setHover] = useState(false);

  let className = 'counter';
  if (hover) {
    className += ' hover';
  }

  return (
    <div
      className={className}
      onPointerEnter={() => setHover(true)}
      onPointerLeave={() => setHover(false)}
    >
      <h1>{person}'ın skoru: {score}</h1>
      <button onClick={() => setScore(score + 1)}>
        Bir ekle
      </button>
    </div>
  );
}

Şu anda, oyuncuyu değiştirdiğinizde state korunmaktadır. Her iki Counter bileşeni de aynı konumdadır ve bu yüzden React bunları, person prop’u değişmiş aynı Counter bileşeni olarak görür.

Ancak konsept olarak bu uygulamada iki farklı sayaç olmalıdır. Kullanıcı arayüzünde aynı konumda görünebilirler ama bir sayaç Taylor için diğer sayaç da Sarah için olmalıdır.

İki sayaç arasında geçiş yaparken state’i sıfırlamanın iki yolu vardır:

  1. Bileşenleri farklı konumlarda render edin
  2. Her bileşene bir key (anahtar) prop’u verin

1. Seçenek: Bileşeni farklı bir konumda render etmek

Eğer bu iki Counter bileşeninin bağımsız olmasını istiyorsanız, iki bileşeni farklı konumda render edebilirsiniz:

import { useState } from 'react';

export default function Scoreboard() {
  const [isPlayerA, setIsPlayerA] = useState(true);
  return (
    <div>
      {isPlayerA &&
        <Counter person="Taylor" />
      }
      {!isPlayerA &&
        <Counter person="Sarah" />
      }
      <button onClick={() => {
        setIsPlayerA(!isPlayerA);
      }}>
        Sonraki oyuncu!
      </button>
    </div>
  );
}

function Counter({ person }) {
  const [score, setScore] = useState(0);
  const [hover, setHover] = useState(false);

  let className = 'counter';
  if (hover) {
    className += ' hover';
  }

  return (
    <div
      className={className}
      onPointerEnter={() => setHover(true)}
      onPointerLeave={() => setHover(false)}
    >
      <h1>{person}'ın skoru: {score}</h1>
      <button onClick={() => setScore(score + 1)}>
        Bir ekle
      </button>
    </div>
  );
}

  • Başlangıçta isPlayerA state’i true’dur. Yani ilk konum Counter state’ini içerir ve ikincisi boştur.
  • ”Sonraki oyuncu” butonuna tıkladığınzda ilk konum temizlenir ancak şimdi ikinci konum Counter’ı içerir.
React bileşenlerinin ağacını içeren diyagram. Üst eleman 'Scoreboard' etiketli ve 'true' değerine eşit 'isPlayerA' etiketli state baloncuğuna sahiptir. Tek alt eleman olan 'Counter' sol taraftadır ve 0 değerine eşit 'count' etiketli state baloncuğuna sahiptir. Soldaki alt elemanın tümü eklendiğini belli edecek şekilde sarı ile vurgulanmıştır.
React bileşenlerinin ağacını içeren diyagram. Üst eleman 'Scoreboard' etiketli ve 'true' değerine eşit 'isPlayerA' etiketli state baloncuğuna sahiptir. Tek alt eleman olan 'Counter' sol taraftadır ve 0 değerine eşit 'count' etiketli state baloncuğuna sahiptir. Soldaki alt elemanın tümü eklendiğini belli edecek şekilde sarı ile vurgulanmıştır.

Başlangıç state’i

React bileşenlerinin ağacını içeren diyagram. Üst eleman 'Scoreboard' etiketli ve 'false' değerine eşit 'isPlayerA' etiketli state baloncuğuna sahiptir. State baloncuğu state'in değiştiğini belirtmek için sarı ile vurgulanmıştır. Soldaki alt eleman, silindiğini belirten sarı 'puf' resmiyle değiştirilmiş ve sağ taraftaki yeni alt eleman eklendiğini belirtecek şekilde sarı renkle vurgulanmıştır. Yeni alt eleman 'Counter' etiketlidir ve değeri 0'a eşit 'count' etiketli state baloncuğu içerir.
React bileşenlerinin ağacını içeren diyagram. Üst eleman 'Scoreboard' etiketli ve 'false' değerine eşit 'isPlayerA' etiketli state baloncuğuna sahiptir. State baloncuğu state'in değiştiğini belirtmek için sarı ile vurgulanmıştır. Soldaki alt eleman, silindiğini belirten sarı 'puf' resmiyle değiştirilmiş ve sağ taraftaki yeni alt eleman eklendiğini belirtecek şekilde sarı renkle vurgulanmıştır. Yeni alt eleman 'Counter' etiketlidir ve değeri 0'a eşit 'count' etiketli state baloncuğu içerir.

”sonraki“‘ne tıklamak

React bileşenlerinin ağacını içeren diyagram. Üst eleman 'Scoreboard' etiketli ve 'true' değerine eşit 'isPlayerA' etiketli state baloncuğuna sahiptir. State baloncuğu state'in değiştiğini belirtmek için sarı ile vurgulanmıştır. Sol taraftaki alt eleman yeni eklendiğini belirtecek şekilde sarı ile vurgulanmıştır. Yeni alt eleman 'Counter' etiketlidir ve değeri 0'a eşit 'count' etiketli state baloncuğu içerir. Sağdaki alt eleman silindiğini belirtecek şekilde sarı 'puf' resmiyle gösterilmiştir.
React bileşenlerinin ağacını içeren diyagram. Üst eleman 'Scoreboard' etiketli ve 'true' değerine eşit 'isPlayerA' etiketli state baloncuğuna sahiptir. State baloncuğu state'in değiştiğini belirtmek için sarı ile vurgulanmıştır. Sol taraftaki alt eleman yeni eklendiğini belirtecek şekilde sarı ile vurgulanmıştır. Yeni alt eleman 'Counter' etiketlidir ve değeri 0'a eşit 'count' etiketli state baloncuğu içerir. Sağdaki alt eleman silindiğini belirtecek şekilde sarı 'puf' resmiyle gösterilmiştir.

Tekrar “sonraki“‘ne tıklamak

Counter bileşeni DOM’dan her silindiğinde state’i de yok edilir. Bu yüzden butona her tıkladığınızda sıfırlanırlar.

Bu çözüm, aynı konumda render edilen birkaç bağımsız bileşeniniz olduğunda kullanışlıdır. Bu örnekte yalnızca iki bileşeniniz var bu yüzden ikisini de JSX’te ayrı ayrı render etmek zor değildir.

2. Seçenek: State’i anahtar ile sıfırlamak

Bir bileşenin state’ini sıfırlamanın daha genel başka bir yolu da vardır.

Listeleri Render Etmek sayfasında anahtar kullanımını görmüş olabilirsiniz. Anahtarlar sadece listeler için değildir! React’in herhangi bir bileşeni ayırt etmesini sağlamak için de anahtarları kullanabilirsiniz. Varsayılan olarak React, bileşenleri ayırt etmek için üst elemandaki sırayı (“ilk sayaç”, “ikinci sayaç”) kullanır. Ancak anahtarlar, React’e bunun yalnızca ilk sayaç veya ikinci sayaç değil de belirli bir sayaç olduğunu, örneğin Taylor’ın sayacı olduğunu söylemenizi sağlar. Bu şekilde React, ağaçta nerede olursa olsun Taylor’ın sayacı olduğunu bilecektir!

Bu örnekte, iki <Counter /> bileşeni JSX’te aynı yerde olsalar bile aynı state’i paylaşmamaktadırlar.

import { useState } from 'react';

export default function Scoreboard() {
  const [isPlayerA, setIsPlayerA] = useState(true);
  return (
    <div>
      {isPlayerA ? (
        <Counter key="Taylor" person="Taylor" />
      ) : (
        <Counter key="Sarah" person="Sarah" />
      )}
      <button onClick={() => {
        setIsPlayerA(!isPlayerA);
      }}>
        Sonraki oyuncu!
      </button>
    </div>
  );
}

function Counter({ person }) {
  const [score, setScore] = useState(0);
  const [hover, setHover] = useState(false);

  let className = 'counter';
  if (hover) {
    className += ' hover';
  }

  return (
    <div
      className={className}
      onPointerEnter={() => setHover(true)}
      onPointerLeave={() => setHover(false)}
    >
      <h1>{person}'ın skoru: {score}</h1>
      <button onClick={() => setScore(score + 1)}>
        Bir ekle
      </button>
    </div>
  );
}

Taylor ve Sarah arasında geçiş yapmak state’i korumamaktadır. Çünkü onlara farklı key’ler (anahtar) verdiniz:

{isPlayerA ? (
<Counter key="Taylor" person="Taylor" />
) : (
<Counter key="Sarah" person="Sarah" />
)}

key (anahtar) belirtmek, React’e üst elemandaki sıraları yerine key’in kendisini konum olarak kullanmasını söyler. Bu nedenle, bileşenleri JSX’te aynı yerde render etseniz bile React onları iki farklı sayaç olarak görecektir ve state’lerini asla paylaşmayacaklardır. Bir sayaç ekranda göründüğü her sefer state’i oluşturulur. Sayaç her silindiğinde ise state’i yok edilir. Aralarında geçiş yapmak, state’lerini tekrar tekrar sıfırlar.

Not

Anahtarların global olarak eşsiz olmadığını unutmayın. Yalnızca üst eleman içindeki konumu belirtirler.

Formu anahtar ile sıfırlamak

State’i anahtar ile sıfırlamak formlarla uğraşırken çok kullanışlıdır.

Bu sohbet uygulamasında, <Chat> bileşeni mesaj input state’ini içermektedir:

import { useState } from 'react';
import Chat from './Chat.js';
import ContactList from './ContactList.js';

export default function Messenger() {
  const [to, setTo] = useState(contacts[0]);
  return (
    <div>
      <ContactList
        contacts={contacts}
        selectedContact={to}
        onSelect={contact => setTo(contact)}
      />
      <Chat contact={to} />
    </div>
  )
}

const contacts = [
  { id: 0, name: 'Taylor', email: 'taylor@mail.com' },
  { id: 1, name: 'Alice', email: 'alice@mail.com' },
  { id: 2, name: 'Bob', email: 'bob@mail.com' }
];

Input’a bir şey yazmayı deneyin ve ardından farklı bir alıcı seçmek için “Alice” veya “Bob” butonuna tıklayın. <Chat> bileşeni ağaçta aynı konumda render edildiği için input state’inin korunduğunu göreceksiniz.

Bir çok uygulamada bu istenen davranış olabilir ancak bu uygulamada değil! Kullanıcının zaten yazdığı bir mesajı yanlış bir tıklama nedeniyle yanlış bir kişiye göndermesine izin vermek istemezsiniz. Bunu düzeltmek için key (anahtar) prop’u ekleyin:

<Chat key={to.id} contact={to} />

Bu, farklı bir alıcı seçtiğinizde Chat bileşeninin, altındaki ağaçtaki herhangi bir state de dahil olmak üzere sıfırdan yeniden oluşturulmasını sağlar. React ayrıca DOM elementlerini tekrar kullanmak yerine yeniden oluşturur.

Şimdi alıcıyı değiştirmek yazılan mesajı temizleyecektir:

import { useState } from 'react';
import Chat from './Chat.js';
import ContactList from './ContactList.js';

export default function Messenger() {
  const [to, setTo] = useState(contacts[0]);
  return (
    <div>
      <ContactList
        contacts={contacts}
        selectedContact={to}
        onSelect={contact => setTo(contact)}
      />
      <Chat key={to.id} contact={to} />
    </div>
  )
}

const contacts = [
  { id: 0, name: 'Taylor', email: 'taylor@mail.com' },
  { id: 1, name: 'Alice', email: 'alice@mail.com' },
  { id: 2, name: 'Bob', email: 'bob@mail.com' }
];

Derinlemesine İnceleme

Silinen bileşenler için state’i korumak

Gerçek bir sohbet uygulamasında, kullanıcı önceki alıcıyı tekrar seçtiğinde input state’ini kurtarmak istersiniz. Artık görünmeyen bir bileşenin state’ini “canlı” tutmanın birkaç yolu vardır:

  • Yalnızca geçerli sohbeti göstermek yerine tüm sohbetleri render edebilir ve kullanmadıklarınızı CSS ile saklayabilirsiniz. Sohbetler ağaçtan silinmezler yani lokal state’leri korunmuş olur. Basit kullanıcı arayüzleri için iyi çalışan bir çözümdür. Ancak gizli ağaçlar büyükse ve çok sayıda DOM node’u içeriyorsa uygulamayı çok yavaşlatabilir.
  • State’i yukarı kaldırabilir ve her alıcı için bekleyen mesajı üst bileşeninde tutabilirsiniz. Bu şekilde, alt bileşenlerin silinmesi önemli değildir çünkü önemli bilgileri tutan üst bileşendir. Bu en çok kullanılan çözümdür.
  • React state’ine ek olarak başka bir kaynak da kullanabilirsiniz. Örneğin, kullanıcı yanlışlıkla sayfayı kapatsa bile mesaj taslağının korunmasını isteyebilirsiniz. Bunu yapmak için, Chat bileşeninin state’ini localStorage'dan okuyabilir ve taslakları da oraya kaydedebilirsiniz.

Hangi stratejiyi seçerseniz seçin, Alice ile sohbet, Bob ile sohbetten kavramsal olarak farklıdır. Bu nedele, <Chat> ağacına mevcut alıcıya göre bir key (anahtar) vermek mantıklıdır.

Özet

  • React, aynı bileşen aynı konumda render edildiği sürece state’i koruyacaktır.
  • State, JSX elemanlarında tutulmaz. State, JSX’i koyduğunuz ağaç konumu ile alakalıdır.
  • Bir alt ağaca farklı bir anahtar vererek state’ini sıfırlamaya zorlayabilirsiniz.
  • Bileşen tanımlarını iç içe yapmayın, aksi takdirde yanlışlıkla state’i sıfırlarsınız.

Problem 1 / 5:
Kaybolan input metnini düzeltin

Bu örnek butona tıkladığınız zaman bir mesaj göstermektedir. Ancak, butona tıklamak aynı zamanda input’u da sıfırlamaktadır. Bu niye olmakta? Butona tıklamanın input metnini sıfırlamayacağı şekilde düzeltin.

import { useState } from 'react';

export default function App() {
  const [showHint, setShowHint] = useState(false);
  if (showHint) {
    return (
      <div>
        <p><i>İpucu: Favori şehriniz?</i></p>
        <Form />
        <button onClick={() => {
          setShowHint(false);
        }}>İpucunu gizle</button>
      </div>
    );
  }
  return (
    <div>
      <Form />
      <button onClick={() => {
        setShowHint(true);
      }}>İpucunu göster</button>
    </div>
  );
}

function Form() {
  const [text, setText] = useState('');
  return (
    <textarea
      value={text}
      onChange={e => setText(e.target.value)}
    />
  );
}