useReducer, bileşeninize bir reducer eklemenizi sağlayan bir React Hook’udur.

const [state, dispatch] = useReducer(reducer, initialArg, init?)

Başvuru Dokümanı

useReducer(reducer, initialArg, init?)

Bileşeninizin state’ini bir reducer ile yönetmek için bileşeninizin üst düzeyinde useReducer çağrısı yapın.

import { useReducer } from 'react';

function reducer(state, action) {
// ...
}

function MyComponent() {
const [state, dispatch] = useReducer(reducer, { age: 42 });
// ...

Daha fazla örnek için aşağıya bakınız.

Parametreler

  • reducer: State’in nasıl güncelleneceğini belirleyen reducer fonksiyonudur. Saf hâlde (pure) olmalı, state’i ve işlemi(action) argüman olarak almalı ve bir sonraki state’i döndürmelidir. State ve işlem herhangi bir tür olabilir.
  • initialArg: Başlangıç state’inin hesaplandığı değerdir. Herhangi bir türden bir değer olabilir. Başlangıç state’inin nasıl hesaplandığı, sonraki init argümanına bağlıdır.
  • isteğe bağlı init: Başlangıç state’ini döndürmesi gereken başlatıcı fonksiyondur. Belirtilmezse, başlangıç state’i initialArg olarak ayarlanır. Aksi takdirde, başlangıç state’i init(initialArg) çağrısının sonucuna ayarlanır.

Dönüş değerleri

useReducer, tam olarak iki değer içeren bir dizi döndürür:

  1. Mevcut state. İlk render sırasında, init(initialArg) veya initialArg (init olmadığında) olarak ayarlanır.
  2. State’i farklı bir değere güncellemenizi ve yeniden render tetiklemenizi sağlayan dispatch fonksiyonu.

Dikkat edilmesi gerekenler

  • useReducer, bir Hook olduğundan, yalnızca bileşeninizin üst düzeyinde veya kendi Hook’larınızda çağırabilirsiniz. Döngüler veya koşullar içinde çağıramazsınız. Buna ihtiyacınız varsa, yeni bir bileşen oluşturun ve state’i taşıyın.
  • Strict Mode’da, React, tesadüfi karışıklıkları bulmanıza yardımcı olmak için reducer ve başlatıcı fonksiyonunuzu iki kez çağırır. Bu, yalnızca geliştirme amaçlı bir davranıştır ve canlı ortamı etkilemez. Reducer ve başlatıcı fonksiyonlarınız saf halde ise (olmaları gerektiği gibi), bu mantığınızı etkilememelidir. Çağrılardan birinin sonucu yoksayılır.

dispatch fonksiyonu

useReducer tarafından döndürülen dispatch fonksiyonu, state’i farklı bir değere güncellemenizi ve yeniden render tetiklemenizi sağlar. dispatch işlevine tek argüman olarak eylemi iletmelisiniz:

const [state, dispatch] = useReducer(reducer, { age: 42 });

function handleClick() {
dispatch({ type: 'incremented_age' });
// ...

React, dispatch fonksiyonuna ilettiğiniz eylemi ve geçerli state ile çağırdığınız reducer işlevinin sonucunu kullanarak, bir sonraki state’i ayarlayacaktır.

Parametreler

  • action: Kullanıcı tarafından gerçekleştirilen eylem. Herhangi bir türde bir değer olabilir. Genellikle bir eylem, kendisini tanımlayan bir type özelliği ve isteğe bağlı olarak ek bilgi içeren diğer özellikler olan bir nesne olarak temsil edilir.

Dönüş değerleri

dispatch fonksiyonları bir dönüş değeri içermez.

Dikkat edilmesi gereken noktalar

  • dispatch fonksiyonu, sadece bir sonraki render işlemi için state değişkenini günceller. Eğer dispatch fonksiyonunu çağırdıktan sonra state değişkenini okursanız, çağrı öncesinde ekranda olan eski değeri elde edersiniz.

  • Eğer sağladığınız yeni değer, bir Object.is karşılaştırması ile belirlendiği gibi, mevcut state ile aynı ise React elemanı ve alt elemanlarının yeniden render edilmesini atlar. Bu bir optimizasyonudur. React, sonucu yok saymadan önce yine de bileşeninizi çağırması gerekebilir ancak bu kodunuzu etkilememelidir.

  • React, state güncellemelerini toplu halde işler. Tüm olay yöneticileri çalıştırıldıktan ve kendi set fonksiyonlarını çağırdıktan sonra ekranı günceller. Bu, tek bir olay sırasında birden fazla yeniden render işlemini önler. React’ı ekranı güncellemeye zorlamanız gereken nadir durumlarda, örneğin DOM’a erişmek için, flushSync kullanabilirsiniz.


Kullanım

Bir bileşene reducer eklemek

Bileşeninizin state’ini reducer ile yönetmek için, useReducer’ı bileşeninizin en üst düzeyinde çağırın.

import { useReducer } from 'react';

function reducer(state, action) {
// ...
}

function MyComponent() {
const [state, dispatch] = useReducer(reducer, { age: 42 });
// ...

useReducer, tam olarak iki öğe içeren bir dizi döndürür:

  1. Bu state değişkeninin mevcut state’i, başlangıçta sağladığınız başlangıç state’i.
  2. Etkileşime yanıt olarak değiştirmenize olanak tanıyan dispatch fonksiyonu.

Ekrandaki içeriği güncellemek için, kullanıcının yaptığı işlemi temsil eden bir nesne ile dispatch fonksiyonunu çağırın. Bu nesne bir eylem olarak adlandırılır:

function handleClick() {
dispatch({ type: 'incremented_age' });
}

React, reducer fonksiyonunuzu çağırırken, mevcut state’i ve eylemi aktaracaktır. Reducer fonksiyonunuz, sonraki state’i hesaplayacak ve döndürecektir. React, bu sonraki state’i saklayacak, bileşeninizi bu state ile yeniden render edecek ve kullanıcı arayüzünü güncelleyecektir.

import { useReducer } from 'react';

function reducer(state, action) {
  if (action.type === 'incremented_age') {
    return {
      age: state.age + 1
    };
  }
  throw Error('Bilinmeyen eylem.');
}

export default function Counter() {
  const [state, dispatch] = useReducer(reducer, { age: 42 });

  return (
    <>
      <button onClick={() => {
        dispatch({ type: 'incremented_age' })
      }}>
        Yaşı artır
      </button>
      <p>Merhaba! {state.age} yaşındasın.</p>
    </>
  );
}

useReducer, useState ile çok benzerdir, ancak state güncelleme mantığını olay yöneticilerinden bileşeninizin dışındaki tek bir bir fonksiyona taşımanıza olanak tanır. useState ve useReducer arasında seçim yapma hakkında daha fazla bilgi edinin.


Reducer fonksiyonu yazmak

Reducer fonksiyonu şöyle tanımlanır:

function reducer(state, action) {
// ...
}

Ardından, sonraki state’i hesaplayacak ve döndürecek olan kodu yazmanız gerekiyor. Geleneksel olarak, bunu bir switch ifadesi olarak yazmak yaygındır. switch ifadesindeki her case için, bir sonraki state’i hesaplayın ve döndürün.

function reducer(state, action) {
switch (action.type) {
case 'incremented_age': {
return {
name: state.name,
age: state.age + 1
};
}
case 'changed_name': {
return {
name: action.nextName,
age: state.age
};
}
}
throw Error('Bilinmeyen eylem: ' + action.type);
}

Eylemlerin herhangi bir şekli olabilir. Geleneksel olarak, eylemi tanımlayan bir type özelliği olan nesnelerin geçirilmesi yaygındır. Bu özellik, reducer’ın bir sonraki state’i hesaplamak için ihtiyaç duyduğu minimal bilgiyi içermelidir.

function Form() {
const [state, dispatch] = useReducer(reducer, { name: 'Taylor', age: 42 });

function handleButtonClick() {
dispatch({ type: 'incremented_age' });
}

function handleInputChange(e) {
dispatch({
type: 'changed_name',
nextName: e.target.value
});
}
// ...

Eylem türü adları bileşeninizle ilgilidir. Her eylem, birden çok veri değişikliğine yol açsa bile, yalnızca bir etkileşimi tanımlar. State’in şekli rastgeledir, ancak genellikle bir nesne veya bir dizi olacaktır.

Daha fazla bilgi için state mantığını reducer’a çıkarma makalesini okuyun.

Tuzak

State salt okunurdur. State içindeki nesneleri veya dizileri değiştirmeyin:

function reducer(state, action) {
switch (action.type) {
case 'incremented_age': {
// 🚩 State'teki bir nesneyi şu şekilde değiştirmeyin:
state.age = state.age + 1;
return state;
}

Bunun yerine, reducer’ınızdan her zaman yeni nesneler döndürün:

function reducer(state, action) {
switch (action.type) {
case 'incremented_age': {
// ✅ Bunun yerine, yeni bir nesne döndürün
return {
...state,
age: state.age + 1
};
}

Daha fazla bilgi edinmek için state içindeki nesneleri güncelleme ve state içindeki dizileri güncelleme makalelerini okuyun.

Temel useReducer örnekleri

Örnek 1 / 3:
Form (nesne)

Bu örnekte, reducer iki alanı olan bir state nesnesini yönetir: name ve age.

import { useReducer } from 'react';

function reducer(state, action) {
  switch (action.type) {
    case 'incremented_age': {
      return {
        name: state.name,
        age: state.age + 1
      };
    }
    case 'changed_name': {
      return {
        name: action.nextName,
        age: state.age
      };
    }
  }
  throw Error('Bilinmeyen eylem: ' + action.type);
}

const initialState = { name: 'Taylor', age: 42 };

export default function Form() {
  const [state, dispatch] = useReducer(reducer, initialState);

  function handleButtonClick() {
    dispatch({ type: 'incremented_age' });
  }

  function handleInputChange(e) {
    dispatch({
      type: 'changed_name',
      nextName: e.target.value
    }); 
  }

  return (
    <>
      <input
        value={state.name}
        onChange={handleInputChange}
      />
      <button onClick={handleButtonClick}>
        Yaşı artır
      </button>
      <p>Merhaba, {state.name}. {state.age} yaşındasın.</p>
    </>
  );
}


Başlangıç state’ini yeniden oluşturmayı önleme

React, başlangıç state’ini bir kez kaydeder ve sonraki render işlemlerinde bunu görmezden gelir.

function createInitialState(username) {
// ...
}

function TodoList({ username }) {
const [state, dispatch] = useReducer(reducer, createInitialState(username));
// ...

createInitialState(username)’in sonucu sadece ilk render işlemi için kullanılmasına rağmen, hala her render işleminde bu fonksiyonu çağırıyorsunuz. Bu, büyük diziler oluşturuyorsa veya maliyetli hesaplamalar yapıyorsa israf olabilir.

Bunu çözmek için, bunu üçüncü argüman olarak useReducer’a bir initializer fonksiyon olarak geçebilirsiniz:

function createInitialState(username) {
// ...
}

function TodoList({ username }) {
const [state, dispatch] = useReducer(reducer, username, createInitialState);
// ...

Yukarıdaki örnekte, createInitialState bir username argümanı alır. Başlatıcı fonksiyonunuz başlangıç state’ini hesaplamak için herhangi bir bilgiye ihtiyacı yoksa, null’i useReducer’ın ikinci argümanı olarak geçebilirsiniz.

Dikkat edin ki, createInitialState kendisi olan fonksiyonu geçiriyorsunuz ve çağrı sonucu olan createInitialState()’i değil. Bu şekilde, başlatma işleminden sonra başlangıç state’i yeniden oluşturulmaz.

Başlatıcı fonksiyonu geçirmenin ve başlangıç state'ini doğrudan geçirmenin farkı

Örnek 1 / 2:
Başlatıcı fonksiyonunu geçirme

Bu örnek başlatıcı fonksiyonunu geçirir, bu nedenle createInitialState fonksiyonu yalnızca başlatma sırasında çalışır. Girdiye yazdığınız gibi, bileşen yeniden render olduğunda çalışmaz.

import { useReducer } from 'react';

function createInitialState(username) {
  const initialTodos = [];
  for (let i = 0; i < 50; i++) {
    initialTodos.push({
      id: i,
      text: username + "'nun görevi #" + (i + 1)
    });
  }
  return {
    draft: '',
    todos: initialTodos,
  };
}

function reducer(state, action) {
  switch (action.type) {
    case 'changed_draft': {
      return {
        draft: action.nextDraft,
        todos: state.todos,
      };
    };
    case 'added_todo': {
      return {
        draft: '',
        todos: [{
          id: state.todos.length,
          text: state.draft
        }, ...state.todos]
      }
    }
  }
  throw Error('Bilinmeyen eylem: ' + action.type);
}

export default function TodoList({ username }) {
  const [state, dispatch] = useReducer(
    reducer,
    username,
    createInitialState
  );
  return (
    <>
      <input
        value={state.draft}
        onChange={e => {
          dispatch({
            type: 'changed_draft',
            nextDraft: e.target.value
          })
        }}
      />
      <button onClick={() => {
        dispatch({ type: 'added_todo' });
      }}>Ekle</button>
      <ul>
        {state.todos.map(item => (
          <li key={item.id}>
            {item.text}
          </li>
        ))}
      </ul>
    </>
  );
}


Sorun giderme

Bir işlem yaptım, ancak state’i yazdırdığımda eski değerini veriyor

dispatch fonksiyonunu çağırmak çalışan kodda state’i değiştirmez:

function handleClick() {
console.log(state.age); // 42

dispatch({ type: 'incremented_age' }); // 43 ile bir yeniden render isteği
console.log(state.age); // Hâlâ 42!

setTimeout(() => {
console.log(state.age); // Hâlâ 42!
}, 5000);
}

Bu, state’in bir anlık görüntü gibi davrandığı için böyle olur. State güncellendiğinde, yeni state değeriyle başka bir yeniden render isteği yapılır, ancak zaten çalışan olay yöneticinizdeki state JavaScript değişkenini etkilemez.

Bir sonraki state değerini tahmin etmeniz gerekiyorsa, reducer’ı kendiniz çağırarak manuel olarak hesaplayabilirsiniz:

const action = { type: 'incremented_age' };
dispatch(action);

const nextState = reducer(state, action);
console.log(state); // { age: 42 }
console.log(nextState); // { age: 43 }

Bir işlem yaptım, ancak ekran güncellenmiyor

React, Object.is karşılaştırması ile belirlendiği gibi bir sonraki state önceki state ile eşitse, güncellemenizi yok sayar. Bu genellikle doğrudan state içinde bir nesne veya bir dizi değiştirdiğinizde olur:

function reducer(state, action) {
switch (action.type) {
case 'incremented_age': {
// 🚩 Hatalı: mevcut nesneyi değiştirme
state.age++;
return state;
}
case 'changed_name': {
// 🚩 Hatalı: mevcut nesneyi değiştirme
state.name = action.nextName;
return state;
}
// ...
}
}

Mevcut bir state nesnesini değiştirip geri döndürdüğünüz için React güncellemeyi görmezden geldi. Bunu düzeltmek için, onları mutasyona uğratmak yerine, her zaman state içindeki nesneleri güncelleyerek ve state içindeki dizileri güncelleyerek emin olmanız gerekir:

function reducer(state, action) {
switch (action.type) {
case 'incremented_age': {
// ✅ Doğru: yeni bir nesne oluşturmak
return {
...state,
age: state.age + 1
};
}
case 'changed_name': {
// ✅ Doğru: yeni bir nesne oluşturmak
return {
...state,
name: action.nextName
};
}
// ...
}
}

Dispatch işleminden sonra reducer state’in bir kısmı tanımsız (undefined) oluyor.

Yeni state’i döndürürken her case dalının mevcut tüm alanları kopyaladığından emin olun:

function reducer(state, action) {
switch (action.type) {
case 'incremented_age': {
return {
...state, // Bunu unutma!
age: state.age + 1
};
}
// ...

Yukarıdaki ...state olmadan, döndürülen yeni state yalnızca age alanını ve başka hiçbir şeyi içermeyecektir.


Dispatch işleminden sonra tüm reducer state’i tanımsız (undefined) oluyor.

Eğer state beklenmedik şekilde undefined olursa, muhtemelen case state’lerinden birinde state döndürmeyi unutuyorsunuz veya eylem türünüz herhangi bir case ifadesine uymuyor. Bunun sebebini bulmak için, anahtar kelime switch’in dışında bir hata yaratın:

function reducer(state, action) {
switch (action.type) {
case 'incremented_age': {
// ...
}
case 'edited_name': {
// ...
}
}
throw Error('Bilinmeyen eylem:: ' + action.type);
}

Böyle hataları yakalamak için TypeScript gibi bir statik tip denetleyicisi de kullanabilirsiniz.


”Too many re-renders” hatası alıyorum

Too many re-renders. React limits the number of renders to prevent an infinite loop. hatası alabilirsiniz. Bu genellikle, işlemi koşulsuz bir şekilde render sırasında gönderdiğiniz anlamına gelir, böylece bileşeniniz döngüye girer: render, gönderim (bu da bir yeniden render yapar), render, gönderim (bu da bir yeniden render yapar) ve böyle devam eder. Bu sıklıkla, bir olay yöneticisi belirleme hatası nedeniyle oluşur:

// 🚩 Yanlış: yöneticiyi yeniden render sırasında çağırır.
return <button onClick={handleClick()}>Tıkla</button>

// ✅ Doğru: olay yöneticisini aşağıya aktarır.
return <button onClick={handleClick}>Tıkla</button>

// ✅ Doğru: iç içe bir fonksiyonu aktarır.
return <button onClick={(e) => handleClick(e)}>Tıkla</button>

Bu hatanın nedenini bulamazsanız, konsoldaki hatanın yanındaki ok’a tıklayın ve JavaScript yığınını (stack) tarayarak hatadan sorumlu belirli dispatch fonksiyonu çağrısını bulun.


Reducer veya başlatıcı (initializer) fonksiyonlarım iki kez çalışıyor.

Strict Mode içinde, React reducer ve başlatıcı (initializer) fonksiyonlarınızı iki kez çağırır. Bu, kodunuzu bozmamalıdır.

Bu yalnızca geliştirme sırasında gerçekleşen davranış, bileşenleri saf olarak tutmanıza yardımcı olur. React, çağrılardan birinin sonucunu kullanır ve diğer çağrının sonucunu yoksayar. Bileşen, başlatıcı ve azaltıcı foknisyonlarınız saf halde olduğu sürece, bu mantığınızı etkilememelidir. Ancak yanlışlıkla saf halde olmayan foknisyonlarınız varsa, bu hataları fark etmenize yardımcı olur.

Örneğin, aşağıdaki saf halde olmayan reducer fonksiyonu, state’deki bir diziyi değiştirir:

function reducer(state, action) {
switch (action.type) {
case 'added_todo': {
// 🚩 Hata: state değiştirme (mutate)
state.todos.push({ id: nextId++, text: action.text });
return state;
}
// ...
}
}

React, reducer fonksiyonunuzu iki kez çağırdığı için, yapılacakların iki kez eklendiğini göreceksiniz, bu yüzden bir hatanın olduğunu bileceksiniz. Bu örnekte, hatayı düzeltmek için diziyi değiştirmek yerine ona yeni bir dizi atayabilirsiniz:

function reducer(state, action) {
switch (action.type) {
case 'added_todo': {
// ✅ Doğru: yeni bir state ile değiştirme
return {
...state,
todos: [
...state.todos,
{ id: nextId++, text: action.text }
]
};
}
// ...
}
}

Artık bu reducer fonksiyonu saf halde olduğuna göre, bir kez daha çağrılması davranışta fark yaratmaz. Bu, React’in iki kez aramakla hataları bulmanıza yardımcı olmasının sebebidir. Sadece bileşen, başlatıcı ve reducer fonksiyonlar saf halde olmalıdır. Olay yöneticisi saf halde olmak zorunda değildir, bu nedenle React asla olay yöneticinizi iki kez çağırmaz.

Daha fazla bilgi edinmek için bileşenleri saf olarak tutma konusunu okuyun.