Merhabalar.

Bu yazım useReducer Hook’unu nasıl kullanabileceğimizi ve detaylarını örnek projemiz üzerinde göreceğiz.

Bir önceki yazımda kavramsal olarak ele almıştım.

Şimdi basit bir süper market uygulaması yazarak useReducer kavramını anlamaya çalışalım.

Yine bütün kodları verelim temel yapıyı oluşturalım. Üzerine reducer’larımıza bakalım.

App.js

import './App.css';

import ShopMarket from './Components/ShopMarket/ShopMarket';

function App() {

  return (

    <div className="Outer">

      <div className="Appp">

      <ShopMarket />

      </div>

    </div>

  );
}

export default App;

ShopMarket.css

.shop-market {
    display: flex;
    flex-direction: column;
    align-items: start;
    height: 85vh;
    width: 100%;
    padding: 20px;
}

.product{
    border: 2px solid #0056b3;
    border-radius: 30px;
    display: flex;
    flex-direction: rows;
    justify-content: space-between;
    padding: 20px;

}

.cart {
    display: flex;
    flex-direction: row;
    align-items: start;
    gap: 20px;
    padding: 30px;
    border: 1px solid #413e3e;
    border-radius: 30px;    
    margin-bottom: 10px;
    margin-top: 30px;
}

.product h2, .cart-item h2 {
    margin: 0 0 10px 0;
}

.product p, .cart-item p {
    margin: 0 0 10px 0;
}

button {
    padding: 5px 10px;
    border: none;
    background-color: #007BFF;
    color: white;
    cursor: pointer;
}

button:hover {
    background-color: #0056b3;
}

ShopMarket.jsx

import React, { useReducer } from 'react';
import './ShopMarket.css';

const shopInitialState = {
    products: [
        { id: 1, name: 'Product 1', price: 100, image: 'https://picsum.photos/200' },
        { id: 2, name: 'Product 2', price: 200, image: 'https://picsum.photos/200' },
        // ...
    ],
    cart: []
};

function shopReducer(state, action) {
    switch (action.type) {
        case 'ADD_TO_CART':
            return {
                ...state,
                cart: [...state.cart, action.payload]
            };
        case 'REMOVE_FROM_CART':
            return {
                ...state,
                cart: state.cart.filter(product => product.id !== action.payload.id)
            };
        default:
            throw new Error();
    }
}

const ShopMarket = () => {

    const [shopState, dispatchShop] = useReducer(shopReducer, shopInitialState);

    const addToCart = (product) => {
        dispatchShop({ type: 'ADD_TO_CART', payload: product });
    }

    const removeFromCart = (product) => {

        dispatchShop({ type: 'REMOVE_FROM_CART', payload: product });
    }

    return (
        <div className='shop-market'>

            <h1>Shop Market</h1>

            <div className='product'>
                {shopState.products.map(product => (
                    <div key={product.id} className='card'>
                        <img src={product.image} alt="" />
                        <h2>{product.name}</h2>
                        <p>{product.price}</p>
                        <button onClick={() => addToCart(product)}>
                            Add to Cart
                        </button>
                    </div>
                ))}
            </div>
            <h2>Sepet</h2>
            <div className='cart'>

                {shopState.cart.map(product => (
                    <div key={product.id} className='cart-item'>
                        <img src={product.image} alt="" />
                        <h3>{product.name}</h3>
                        <p>{product.price}</p>
                        <button onClick={() => removeFromCart(product)}>
                            Remove from Cart
                        </button>
                    </div>
                ))}
            </div>
        </div>
    )
}

export default ShopMarket

Oluşacak görüntümüz aşağıdaki gibi olmalı.

Amaç

Amacımız; Ürünlerden Add to Cart butonuna basınca ürünün sepete eklenmesini, Remove from Cart butonuna basınca sepetten çıkarılmasını istiyorum.

Ürün ve Sepet İçin InitialState Oluştur

Öncelikle ürünlerimi ve sepet listesinin ilk halini oluşturacağım.

2 adet ürün bilgim var. ve cart listesi ilk başta boş.

const shopInitialState = {
    products: [
        { id: 1, name: 'Product 1', price: 100, image: 'https://picsum.photos/200' },
        { id: 2, name: 'Product 2', price: 200, image: 'https://picsum.photos/200' },

    ],
    cart: []
};

Bu benim state’im ve burada ürünler cart’a eklendiğinde ya da çıkarıldığında Cart listem yani shopInitialState güncellenecek.

İki durumum ile burası etkilendiği için useReducer ile kontrol etmek istiyorum durumu.

useReducer Import

useReducer Hook’unu kullanabilmek için öncelikle react’ten import etmek gerekiyor.

import React, { useReducer } from 'react';

useReducer Hook’u Oluştur

Çalışmasını istediğim useReducer Hook’umu tanımlıyorum. Aşağıdaki yapı useReducer’ın standart yapısıdır.

const [state, dispatch] = useReducer(reducer, initialState);

useReducer hook’u iki ana parametre alır: birinci parametre reducer ve ikinci parametre ise initialState.

reducer ismiyle verdiğim işlemi yapacak fonksiyon birazdan bunun tanımlaması yapılacak. initialState’de değişimi sağlanacak olan değişkenimizin ilk hali.

Burada reducer diye belirttiğim fonksiyon ismi kullanıcı tanımına bırakılmıştır. Örneğin fonksiyonun ismi marketReducer olsaydı burada reducer yazan yere bu ismi yazmamız gerekecekti.

reducer fonksiyonun görevi, mevcut durumu ve bir eylemi temsil eden bir nesneyi alarak yeni bir durum üretmektir.

initialState: Reducer fonksiyonunun başlangıç durumunu temsil eden bir nesnedir. Bu, genellikle uygulamanızın başlangıcındaki varsayılan durumu belirtir. Başlangıç durumu, uygulamanızın ilk yüklendiği durumu temsil eder. Bu durum, reducer fonksiyonu tarafından güncellenecek ve component’ın yeniden render edilmesine neden olacaktır.

useReducer hook’u bir dizi değeri döndürür: [state, dispatch]. Bu dizi, mevcut durumu (state) ve durumu güncellemek için kullanılan dispatch fonksiyonunu içerir.

state: Mevcut durumu temsil eder. İlk başta initialState ile başlar ve daha sonra reducer fonksiyonu tarafından güncellenir.

dispatch: Eylemleri tetiklemek için kullanılan bir fonksiyondur. Bu fonksiyon, reducer fonksiyonunu çağırarak durumu günceller ve component’ı yeniden render etmeye neden olur.

Aynı şekilde state ve dispatch diye belirttiğim değerlerde kullanıcıya bırakılmış alanlardır.

Aşağıdaki bizim bu uygulama için oluşturduğumuz useReducer’ımız.

const [shopState, dispatchShop] = useReducer(shopReducer, shopInitialState);

Bu şekilde aynı component içinde birden fazla useReducer oluşturulabilir.

Reducer Fonksiyonumuz

function shopReducer(state, action) {
    switch (action.type) {
        case 'ADD_TO_CART':
            return {
                ...state,
                cart: [...state.cart, action.payload]
            };
        case 'REMOVE_FROM_CART':
            return {
                ...state,
                cart: state.cart.filter(product => product.id !== action.payload.id)
            };
        default:
            throw new Error();
    }
}

Dispatch edilen eylemlere göre durumu güncelleyen bir reducer fonksiyonu tanımlıyoruz.

Bu fonksiyonumuz İki eylem türünü ele alıyor: ADD_TO_CART ve REMOVE_FROM_CART.

ADD_TO_CART ile sepete belirtilen ürünü ekler.

REMOVE_FROM_CART ile belirtilen ürünü sepetteki ürünlerden çıkarır.

Dispatch Fonksiyonlarının Tetiklenmesi ve Reducer’ın İşlemesi

Şimdi bu reducer’ın çağrıldığı yere bakalım.

 <button onClick={() => addToCart(product)}>
 Add to Cart
 </button>

Add to Cart butonuna basıldığında onClick event’i tetikleniyor. Bu event addToCart() fonksiyonuna bağlı ve product isminde bir parametre alıyor.

Fonksiyonumuz ise şunu yapıyor.

const addToCart = (product) => {
        dispatchShop({ type: 'ADD_TO_CART', payload: product });
    }

dispatch fonksiyonlarının standart bir yapıları vardır ve genellikle bir eylemi temsil eden bir nesneyi alırlar. Bu nesne genellikle “type” adlı bir özelliğe sahip bir objedir. “type”, eylemin türünü belirtir ve reducer fonksiyonu bu türü temel alarak durumu günceller.

Ancak, bu standart yapının ötesinde ek bilgiler içerebilecek bir payload özelliği de olabilir. Bu, eylemin daha fazla detay eklemek için kullanılabileceği bir mekanizmadır.

Bu şekilde, dispatch fonksiyonları, eylemin türünü anlamak ve gerekirse ek bilgileri içermek için kullanılır. Reducer fonksiyonu, bu eylemi temel alarak durumu günceller. Bu yapı, genellikle öngörülebilir ve okunabilir bir durum yönetimi sağlar.

Fonksiyonumuz Ne Yapıyor?

Ben useReducer’ımı tanımlarken; tetikleyici fonksiyonumu dispatchShop olarak belirtmiştim. O yüzden bu fonksiyonumu çağırdım. Bu fonksiyona bir obje vermem gerekiyor. Bu obje’ninde type ve payload isminde key değerleri olmalı.

type olarak hangi işlemi yapacaksa onu veriyorum. Bu reducer fonksiyonum içinde karşılaştırma yapmamı sağlayacak.

ADD_TO_CART diyorum.

payload kısmında shopInıtialState’i güncelleyecek verilerimi gönderiyorum. Bu payload tek bir değişken değer alabileceği gibi obje de alabilir. Burada product bilgisini gönderiyorum. Bu product reducer fonksiyonu içinde işlenecek.

shopReducer Çalışır

function shopReducer(state, action) {
    switch (action.type) {
        case 'ADD_TO_CART':
            return {
                ...state,
                cart: [...state.cart, action.payload]
            };
        case 'REMOVE_FROM_CART':
            return {
                ...state,
                cart: state.cart.filter(product => product.id !== action.payload.id)
            };
        default:
            throw new Error();
    }
}

useReducer’a parametre olarak gelen değere action parametresi ile erişilir. type değerini almak için action.type deriz. payload değerini almak için action.payload.

Fonksiyon içinde bir switch case yapısı çalışıyor.

action.type ile gelen değer ADD_TO_CART olduğu için ilk adımdaki bölüme giriyor.

Güncellenecek olan değişken state parametresi ile gönderilir. Bu shopInitialState’e denk geliyor.

return {
   ...state,
   cart: [...state.cart, action.payload]
};

Burada state’in son durumunu return ediyoruz. Öncelikle diyoruz ki var olan listenin tamamını bana getir.

Sonra değişim cart nesnesi içinde olacağı için, …state.cart ile var olan değerlerini alıp üzerine action.payload ile gelen product’ı ekliyoruz. Böylece state yeni haliyle güncellenmiş oluyor.

Remove From Cart Reducer İnceleme

Ürünü sepetten kaldırmak istediğimizde ürünü kaldırma butonuna tıklarız.

<button onClick={() => removeFromCart(product)}> Remove from Cart </button>

Bu tıklama removeFromCart fonksiyonunu tetikler ve kendisi üzerinde product’ı parametre olarak gönderir.

const removeFromCart = (product) => {


    dispatchShop({ type: 'REMOVE_FROM_CART', payload: product });

}

Bu fonksiyon yine dispatchShop fonksiyonunu tetikler. Bu sefer type olarak REMOVE_FROM_CART gönderiyoruz. Payload olarak yine product gönderiliyor.

Bu noktadan sonra useReducer devreye giriyor ve shopReducer fonksiyonu tetikleniyor. action.type da gelen değer REMOVE_FROM_CART olduğu için switch’in 2. kısmı tetikleniyor.

case 'REMOVE_FROM_CART':
            return {
                ...state,
                cart: state.cart.filter(product => product.id !== action.payload.id)

case 'REMOVE_FROM_CART': Bu satır, reducer fonksiyonunda bir durum yönetimi durumu tanımlar. Yani, eğer eylemin türü ‘REMOVE_FROM_CART’ ise, aşağıdaki kodları çalıştır.

return { ...state, cart: state.cart.filter(product => product.id !== action.payload.id) }: Bu satır, yeni bir durum nesnesi döndürerek mevcut durumu günceller. Bu güncelleme, sepet içinde belirli bir ürünü kaldırmakla ilgilidir.

{ ...state,: Bu kısım, mevcut durum nesnesinin tüm özelliklerini (state içindekileri) kopyalar. Böylece, reducer fonksiyonu mevcut durumu değiştirmek yerine yeni bir durum nesnesi oluşturur ve bu nesnenin içine eski durumdaki değerleri kopyalar.

cart: state.cart.filter(product => product.id !== action.payload.id) }: Bu kısım, sepet içindeki ürünleri filtreleyerek belirli bir ürünü kaldırır. filter fonksiyonu, belirtilen koşulu sağlayan öğeleri içeren yeni bir dizi döndürür. Bu durumda, product.id !== action.payload.id koşulu, sepet içindeki ürünlerden eylem nesnesindeki id‘ye sahip olanı hariç tutar.

Bu kod, alışveriş sepetinden bir ürünü kaldırmak amacıyla kullanılır. action.payload.id ile belirtilen ürünün id özelliğine sahip olmayan diğer ürünler, yeni durumda kalarak sepette kalır.

Uygulama Test

useReducer Hook’u bu şekildeydi.

Bir sonraki yazımda görüşmek üzere.


Murat Bilginer
21 Şubat 1992'de doğdum. Endüstri Mühendisi olarak lisansımı 2016 yılında tamamladım. Industryolog Akademi - NGenius oluşumlarının kurucusuyum. Şu anda kendi şirketim Brainy Tech ile Web ve Mobil Geliştirme, AWS, Google Cloud Platform Sistemleri için DevOps, Big Data Analiz ve Görselleştirme hizmetleri sunmakta ve Online Eğitimler vermekteyiz.